skip to content

A remembered-device token with no absolute expiry survives a password reset — what has your second factor become, and what fixes it?

level: seniorimportance: must knowfreq 48%

answer

  1. convenience is not authority
  2. measure the clock from issue
  3. re-issue copies the deadline forward
  4. credential change deletes every row
  5. skips the routine prompt, nothing else

basics

~20 s

It has become a permanent bearer credential — anyone holding that value signs in with a password alone, forever. Fix it with an absolute lifetime measured from issue, and deletion of every device record when the account's credentials change.

solid answer

~50 s

Two controls have failed at once. Without an absolute expiry the record never ends for a machine in regular use, so a value that leaks keeps skipping the second factor indefinitely; and by surviving a password reset it makes the reset cosmetic, because the reset was the account holder's way of saying *something has gone wrong, cut everything off*. The fix is an `absolute_expires_at` fixed at issue and never recomputed — if you re-issue the value on each use, copy the original deadline forward rather than restarting it — plus deleting every remembered-device row for the account on a password change or reset, a second-factor re-enrolment, and any explicit "this wasn't me" report. And keep the grant narrow: the record skips the routine second-factor prompt on an ordinary sign-in and satisfies no demand for fresh proof of the person.

code

pseudocode · 20 lines
pseudocode
on sign_in(account, password, device_cookie):
    require password_ok(account, password)        # first factor, always

    record = find_device_record(account, hash(device_cookie))
    if record is null:
        return CHALLENGE
    if now >= record.absolute_expires_at:
        delete(record)
        return CHALLENGE

    # value rotation only: the deadline is copied, never recomputed
    new_value           = random_256_bits()
    record.token_hash   = hash(new_value)
    record.last_used_at = now
    save(record)
    set_cookie(new_value, expires = record.absolute_expires_at)
    return SKIP_SECOND_FACTOR

on credentials_changed(account):                  # reset, change, re-enrolment
    delete_all_device_records(account)

go deeper

for a junior

Remember that a remembered-device record must have an end date fixed when it is issued, and that changing or resetting the account's password gets rid of every one of those records.

for a middle

Explain why re-issuing the value on each use has to copy the original deadline rather than compute a new one, and why an expiry that slides forward never ends for a device in regular use.

for a senior

Price the lifetime out loud: prompts saved per record against how long a leaked value keeps working. Name the load-bearing control — the absolute ceiling — and say what is left standing once it is removed.

for a principal

The judgment call is what the skip may cover at all. Argue that it buys back one routine prompt and nothing else, and that widening it to satisfy fresh-proof demands is how a convenience quietly becomes an authorisation.

## The clock the record must run on A remembered-device record has exactly one clock that matters: an **absolute lifetime measured from the moment it was issued**. Write `absolute_expires_at` when the row is created, derive it from `issued_at`, and treat it as immutable for the life of that row. Everything else about the record — how often it is used, whether the value was re-issued, what the member is doing — leaves that deadline alone. The alternative that ships by accident is an expiry pushed forward on every use. It is a natural thing to write and it is the defect in the question: a device in weekly use is then trusted **for as long as the member keeps using it**, which is to say indefinitely. The control looks present in the code and is absent in effect. There is exactly one legitimate way the window moves: the old row is deleted and a **new record is issued**, with a new `issued_at` and a new deadline — and issuing a new record is a decision the server makes deliberately, usually behind a fresh second-factor proof, not a side effect of a successful sign-in. ## Re-issue on use, without extending anything Re-issuing the random value each time the record is used is a reasonable thing to do: it limits how long any single value has been sitting in one place. But it is a *value* rotation, not a *record* rotation. The row keeps its `issued_at` and its `absolute_expires_at`; only `token_hash` and `last_used_at` change, and the cookie that carries the new value must still be set to expire no later than the original deadline. Get this backwards and the re-issue becomes exactly the sliding expiry you were trying to avoid, wearing a more careful name. ## The grant is narrow, and the narrowness is the design The second thing a long-lived record gives away is scope, and this one is worth stating as a rule: | The record may | The record may not | |---|---| | Skip the routine second-factor prompt on an ordinary sign-in | Stand in as fresh proof for an operation that demands it | | Be presented alongside the first factor, never instead of it | Replace the password on any path | | Exist while the account holder is signed out | Confer any authority of its own once signed in | The reason is the same one that governs the binding: possession of a machine is not evidence about the person in front of it. A record that can satisfy a fresh-proof demand has converted a convenience into an authorisation, and the borrowed tablet in the association's shed now speaks for whichever plot-holder last used it. ## The events that must delete every row Some events are statements about the account's credentials, and every one of them must delete **all** remembered-device rows for that account, not just the one in front of you: 1. **A password change or reset.** Especially a reset: the member is usually telling you something went wrong. A reset that leaves ten devices skipping the second factor has changed almost nothing for the person who already has the old password and one of those devices. 2. **A second-factor enrolment change** — a new authenticator registered, an old one removed. The records were issued against a factor configuration that no longer exists. 3. **An explicit report from the member** ("this wasn't me", "I lost the tablet"). Deleting all rows is the cheap, correct answer; asking them to identify which one is not. Note what is *not* on this list: signing out. Sign-out ends a session and leaves the standing statement about the machine intact, which is the behaviour the member asked for when they ticked the box. ## Pricing the lifetime A senior answer prices the number rather than quoting one. The lifetime buys back prompts: roughly one avoided challenge per sign-in per remembered machine, for as long as the record lives. It costs you a window in which a leaked value signs in with a password alone. So the lifetime is short where the account can act on other people — the association's committee members who edit the plot register or the waiting list — and can be longer where it cannot. The load-bearing control is the **absolute ceiling**. Remove it and everything else still works: the row is still bound to one account and one browser, the value is still hashed at rest, the grant is still narrow — and the account still has one factor in practice for every machine it has ever trusted. Remove the deletion-on-credential-change rule instead and you keep the ceiling, but you lose the only lever the member has to end it early. Those two, together, are what make remembering a device a bounded trade rather than a permanent one.

  • How would you choose the absolute lifetime for an allotment association's plot-holder accounts?
    Price it rather than pick it. Count the prompts a record avoids over its life and set that against how long a leaked value would keep signing in with a password alone. Ordinary plot-holders, who can only see their own plot and the rota, tolerate a longer window; committee accounts that edit the plot register or the waiting list get a short one, because the cost side of the trade is much larger there.
  • A member asks why they are being challenged again when they already ticked "do not ask again".
    Because the tick covered one thing: the routine second-factor prompt on an ordinary sign-in. Anything that demands fresh proof of the person is a different demand, and holding the tablet answers none of it. The honest explanation to give the member is that the machine is remembered, not them — and that the record also has an end date, so it will ask again eventually whatever they do.
  • Should signing out delete the remembered-device record?
    No. Sign-out ends a session; the record is a standing statement that this browser may skip a prompt on a future sign-in, and surviving sign-out is exactly what the member asked for. Deleting it there would make the feature pointless. Deletion belongs to credential events — a password change or reset, a factor re-enrolment, an explicit report from the member — and to the absolute deadline.

A season ticket at the gate, not a signature on a form. The attendant who waves through whoever carries the ticket is right to — the ticket buys the gate, and that is all it was sold to buy. He is wrong the moment someone asks him to witness a signature with it: the ticket proves a ticket turned up, never who is holding it. And a season ticket carries a printed end date precisely because nobody can remember who still has one.

saying these in an interview costs you the question

  • Pushing the expiry forward on every use, so an active device never ages out.
  • Accepting a remembered device as the fresh proof a sensitive action demands.
  • Leaving device records alive after a password reset, so the reset changes little.
  • Choosing the lifetime by what members will tolerate rather than what it grants.
  • Treating a stolen device value as harmless because the password is still needed.
  • Deleting only the record in front of you when a credential changes.