skip to content

When a password-reset link is consumed, what must change besides the stored password?

level: middleimportance: must knowfreq 65%

answer

  1. one write, not a check then a write
  2. spares left behind are still live
  3. a reset is a credential event
  4. notify out of band, not on screen

basics

~20 s

Consumption is one atomic step: mark the token used, set the password, kill the account's other outstanding reset tokens, end its sessions and remembered devices, clear the failed-attempt counts, and notify the member out of band.

solid answer

~50 s

Consume with one conditional write — mark the token used only if it is still unused and unexpired — and set the password in the same transaction, so a double submit cannot land twice. In that step invalidate every other outstanding reset token for the account, because a member who asked three times must not leave two live spares behind. A completed reset is a credential event, so the account's other sessions and remembered-device records go with it; if sign-in state is carried in a self-contained value the server never looks up, that sign-out is not instant and the notice should not pretend otherwise. Then tell the member at the address on file that the password changed and other sessions ended. The request screen itself answers identically whether or not the address belongs to an account.

code

pseudocode · 20 lines
pseudocode
on submit_new_password(raw, new_password):
    h = hash(raw)

    begin transaction
        changed = mark_consumed_if_unused_and_unexpired(h, now())
        if changed == 0:
            rollback
            respond(GENERIC_FAILURE)        # unknown, expired, already used
            return

        account = account_for_reset_hash(h)
        set_password(account, new_password)
        invalidate_other_reset_rows(account)
        end_all_sessions(account)
        forget_remembered_devices(account)
        clear_failed_attempt_counters(account)
    commit

    notify_out_of_band(account, "password changed; other sessions ended")
    respond(SUCCESS)

go deeper

for a junior

Remember that the link works once. Marking it used and writing the new password belong together, so that a second click on the same message changes nothing at all.

for a middle

Explain the conditional write and what a zero-row result means, then list what else the step invalidates: the account's other outstanding links, its live sessions, its remembered devices, its failed-attempt counts.

for a senior

Own the lag. If sign-in state is a self-contained value the server never looks up, "signed out everywhere" is a promise about the next re-issue, and the notice you send should match what actually happens.

for a principal

Settle once, centrally, which credential events force a global sign-out and what the member is told, so every path that touches a credential inherits one rule instead of six screens each inventing their own.

## One write, not a check and then a write The launch-authorisation site's reset endpoint is the one place a password changes with no password presented, so everything it does has to happen exactly once. The gate is a **conditional write**: mark the row consumed *only while it is still unconsumed and unexpired*, and act on how many rows that changed. - **One row changed** — this caller is the one holding the live link, and the flow continues. - **Zero rows changed** — the token was never issued, has expired, or was already used. Same generic failure either way. Read-then-write is the classic defect: the read says "unused", the member double-taps the submit button or a retry fires, and two requests both pass the check before either writes. Setting the new password belongs in the same transaction as the consumption, so the two cannot come apart. ## The spares A member recovering under time pressure asks two or three times and clicks whichever message arrives. Every request that issued a row left a live credential behind. So invalidate the siblings at both ends: 1. **At issue** — a new request supersedes the account's outstanding rows, so at most one link is ever live. 2. **At consumption** — anything still outstanding for that account dies with the one being used. Without this, the link a member abandoned an hour ago still opens the account for as long as its own window runs, and it is the one they never look at again. ## A completed reset is a credential event The reason to reset is usually that the password is no longer trusted. Leaving the account's existing sessions running says the opposite. The policy question — which events fire a global sign-out — is worth settling once, centrally, rather than per screen: | event | fires a global sign-out? | what the member is told | |---|---|---| | completed unauthenticated reset | yes, every session | password changed, other sessions ended | | authenticated password change | yes, all but the one making the change | same notice, sent out of band | | a second factor removed, or a backup code spent | yes | the factor that changed, and when | | an administrator sets a password | yes, every session | who did it and why, so it is not mistaken for theft | | a display-name or preference change | no | nothing | Remembered-device records go with the sessions, or the next sign-in quietly skips the second factor on a machine the attacker is sitting at. The account's failed-attempt counts are cleared too, or a member who has just recovered arrives at a refusal left over from the attack that prompted the reset. **How** that fan-out is built — ending server-side session records, dropping device rows, killing a long-lived credential family — is a separate subject. This path only decides *which* event fires it. ## The lag you have to own If sign-in state is a server-side record the site looks up on every request, the sign-out is immediate: delete the records and the next request is anonymous. If it is a self-contained value the server validates without a lookup, it is not. That value keeps working until it expires or until something forces a re-issue that consults a revocation marker, and the gap can be minutes. Either shape is defensible; quietly telling the member "you are signed out everywhere" when the truth is "within the next few minutes" is not. ## What the member reads The notification is the detection control on this whole path. Sent to the address on file, out of band, it is how a member learns about a reset they did not start — and the only warning they get if the mailbox itself is the thing that was taken. An on-screen confirmation reaches whoever is holding the link, which is exactly the wrong reader when that is not the member. ## The screen that asks for the address The request screen answers the same way for an address that has an account and one that does not: same body, same status. Otherwise the reset form becomes a way to test the club roster against real membership, and every control on the sign-in page is undone by the page next to it. A pause before an error is not a substitute — the response still differs, only later. ## Not on the page load Consume on the submission that sets the password, never on the request that renders the form. Message-scanning systems follow links before a human ever sees them; if opening the URL consumes the token, the member's first click reports that the link was already used, and they ask for another, and that one is eaten too. Keep the rendering path free of side effects.

  • A member says the link reported "already used" on their very first click. What do you check?
    Whether something in the mail path followed the URL first. Link-scanning systems fetch every link in a message, so if rendering the form consumes the token, the scanner spends it before the member arrives. The fix is to make the page load side-effect free and consume only on the submission, then re-issue. Shortening the window makes this worse, not better.
  • Which other credential events should fire the same global sign-out?
    An authenticated password change, with the session making the change usually kept; removal of a second factor or the spending of a backup code; and an administrator setting a password on a member's behalf. Cosmetic profile edits should not. Decide the list once and let every path inherit it, or each screen invents its own rule and one of them will forget.
  • Should a successful reset sign the member straight in?
    It is defensible only if you accept the reset link as the strongest proof that path requires, because it hands a session to whoever held the link. Sending the member to sign in with the new password costs one screen, exercises the credential they just set, and keeps the sign-in path as the only thing that mints a session. On a site where a window is closing, that extra screen is a real cost worth naming.

Changing the lock on the club's equipment shed. Fitting the new barrel is the easy half; the job is not done until the spare keys handed out last season are collected and the people who held them are told.

saying these in an interview costs you the question

  • Checking the token is unused, then writing the password as two unguarded steps
  • Leaving other sessions alive because the password itself changed
  • Consuming the token on the page load that renders the form
  • Answering "no account with that address" on the reset request screen
  • Keeping older reset links alive in case the member uses either one
  • Confirming only on screen, where whoever holds the link is standing