skip to content

On a remembered device a sign-in skips the second factor — what does the server store per device, and what is that token bound to?

level: middleimportance: must knowfreq 55%

answer

  1. a row, not a flag
  2. its own credential, not the session
  3. bound to a browser profile, not a person
  4. store the hash, hand back the value
  5. issue time fixes the absolute expiry

basics

~20 s

A remembered device is its own server-side record — account, a hash of the issued value, issue time, absolute expiry — and the value it hands the browser is bound to one account and one browser profile, never to a person.

solid answer

~50 s

Store a row per remembered device, not a flag on the user: the account it belongs to, a hash of the long random value you handed back, the timestamp it was issued, an absolute expiry derived from that timestamp, a last-used timestamp, and a label the owner will recognise. Hand the browser only the random value, in a cookie scoped to your site, and keep the verifier hashed at rest so a database dump is not a pile of working second-factor skips. The value is bound to one account and to the browser profile that happens to hold the cookie — nothing more. It is not bound to hardware, and it is emphatically not bound to the person sitting in front of that hardware. That is why it may buy back one thing only: the routine second-factor prompt on an ordinary sign-in.

code

json · 8 lines
json
{
  "account_id": "acct_4417",
  "token_hash": "e3b0c44298fc1c149afbf4c8996fb924...",
  "issued_at": "2026-03-04T19:22:07Z",
  "absolute_expires_at": "2026-04-03T19:22:07Z",
  "last_used_at": "2026-03-19T08:41:55Z",
  "label": "shed tablet"
}

go deeper

for a junior

Recall that a remembered device is a row the server stores, not a setting on the user. It holds the account, a hash of the value handed to the browser, and the time that value was issued.

for a middle

Be able to say what the value is bound to and what it is not: one account, one browser profile, a fixed window — and never the person. Explain why the hash is stored rather than the value.

for a senior

Show that you treat it as a separate credential with its own lifecycle, and name the failure you are accepting: a shared machine defeats the binding silently, so the grant must be narrow enough to survive that.

for a principal

Frame it as buying friction back at a known price, and say who owns that price. The lifetime, the width of the grant and the deletion rules are one policy; split them across teams and the skip outlives the credential it was issued against.

## What remembering a device actually is A second factor costs the account holder a prompt on every sign-in. Remembering a device buys some of that friction back: the server lets a long-lived value it issued earlier stand in for a fresh second-factor proof on the ordinary sign-in path. The property that makes the feature work is also the property that makes it dangerous — it is **a standing statement about a machine, not about a sign-in and not about a person**. A standing statement needs somewhere to stand, and that is a row of its own: - **`account_id`** — the account the statement is about. The row is keyed by account *and* by the value handed out, never by "the machine": a server cannot see a machine. - **`token_hash`** — a hash of the long random value returned to the browser. The browser holds the secret; the server holds only a verifier. - **`issued_at`** — the origin of the record's clock. - **`absolute_expires_at`** — derived once from `issued_at`. Nothing that happens afterwards moves it. - **`last_used_at`** — operational: it lets a stale row be spotted, and it helps the owner recognise a row before deleting it. - **`label`** — a human name ("the shed tablet") so deleting the right row is possible at all. Hand the browser exactly one thing: a long random value in a cookie scoped to your site, with the cookie's own expiry set no later than `absolute_expires_at`. Store the hash rather than the value, for the reason any bearer secret is hashed at rest — a dump of this table would otherwise be a working second-factor skip for every account in it, and that skip keeps working for weeks after the dump is noticed. ## What the value is actually bound to This is the part interviewers push on, because most candidates answer it one step too generously. | It is bound to | It is not bound to | |---|---| | One account — the row names it, and the same value presented on another account is simply unknown | The person, who is never observed by any part of this mechanism | | One browser profile, because that is where the cookie physically lives | The hardware: no serial, no chip, nothing the server can attest | | A fixed window starting at `issued_at` | The signed-in session, which starts and ends many times inside that window | The consequence is the sentence this whole subject turns on: **possession of a machine is not evidence about the person in front of it.** A borrowed tablet in a shed, a household laptop, a partner who already knows the password — each defeats the binding completely, and none of them is an attack the record can detect. The record stays honest only if the grant it buys is narrow enough to survive being held by the wrong person. ## Why it is not simply a longer session The tempting simplification is to skip the row and give the signed-in session a much longer life instead. It is the wrong shape, for three reasons: 1. **Different subject.** A session says *this request belongs to a sign-in that happened*. A remembered-device record says *this browser may skip a prompt on some future sign-in*. The second is supposed to survive sign-out; a session that survived sign-out would be a bug. 2. **Different authority.** The session carries the account's full authority for its duration. The device record carries exactly one privilege: not being asked for the second factor on an ordinary sign-in. Merging them hands the machine the whole account. 3. **Different lifecycle.** They are created, renewed and destroyed by different events, and a design that shares one row has to pick which event wins — which is how a sign-out either fails to end a session or silently throws away a year of remembered devices. ## What the sign-in path does with it The order matters and is easy to get backwards. The first factor is presented and verified as usual — the record never skips the password. Only then does the server hash the presented cookie value, look for a live row on **that account**, and read a hit as "do not prompt for the second factor this time". A miss, an expired row, and a value belonging to a different account are all the same outcome: prompt normally. None of them is an error worth reporting to the caller, because the caller without a row is usually just a member on a new browser. ## Where it goes wrong The allotment association keeps one tablet in the shed and every plot-holder borrows it. Half of them tick "do not ask again". The server now holds one row per account, all inside the same browser profile, each ready to skip a prompt for whoever picks the tablet up next. Nothing in the mechanism is broken — the rows are correctly bound, correctly hashed and correctly aged. The design is simply being asked to prove something it never claimed, and the only defences that hold are the ones that limit the grant.

  • Why store only a hash of the remembered-device value rather than the value itself?
    Because the value is a long-lived bearer secret. A dump of the table would otherwise hand an attacker a working second-factor skip for every account listed, and that skip keeps working for the rest of each record's life. Storing a hash means the table alone is useless: the server compares the hash of what was presented, and never needs to reproduce the value.
  • The same member signs in from a second browser on the same machine — should that browser skip the prompt?
    No, and that is the honest cost of the design. The record follows the cookie, so a different browser profile is simply a machine the server has never seen for that account and gets the ordinary challenge. Trying to fix it by keying the row on something machine-wide would mean trusting a signal the server cannot verify, which buys convenience by weakening the only binding the record really has.
  • Two plot-holders share the shed tablet and both tick "do not ask again" — what does the server hold?
    Two independent rows, one per account, both sitting in the same browser profile. Each skips the prompt for its own account only, so neither one lets a member into the other's account. What it does mean is that whoever holds the tablet next skips a prompt on any of those accounts whose password they also know — which is exactly why the grant must stay narrow.

saying these in an interview costs you the question

  • Treating a remembered device as just a much longer session timeout.
  • Storing the device value in plain text, so a dump yields working skips.
  • Assuming the record identifies the human, so it can authorise a sensitive action.
  • Calling it a hardware identity when it only ever follows a browser profile.
  • Thinking one row covers the machine, so any account on it skips the prompt.