skip to content

Credentials & Multi-Factor

A password is one factor; everything else decides the second — what the proof is bound to, how long it keeps counting, and how a locked-out user gets back in. Interviewers probe the weakest path.

on this pageshow

explore

questions

24

A donor portal emails a one-time sign-in code. What does the server write down so it can verify it later?

level: juniorimportance: must knowfreq 58%

answer

  1. server-side state, not the client's
  2. one record per pending sign-in
  3. digest, expiry, consumed marker, guess count
  4. every outcome spends one attempt

basics

~20 s

A pending-attempt record on the server: a digest of the delivered code, the sign-in attempt it belongs to, an expiry a few minutes out, a consumed marker, and the count of failed guesses against it.

solid answer

~40 s

The code is not state the client can be trusted to carry. When the first factor succeeds the server creates a **pending sign-in attempt** and hangs the code's state off it: a digest of the code rather than the code itself, the channel and masked destination it went to, `issuedAt` and a short `expiresAt`, a `consumedAt` marker so a correct code works exactly once, and `failedAttempts` against a fixed `maxAttempts`. Verification loads that one record, checks the cap first, spends one attempt whether the submitted value is wrong, expired or already consumed, and only then compares digests. The record is doing the security work — six digits are guessable on their own, and they are defensible only because what surrounds them is short-lived, single-use and capped.

code

json · 14 lines
json
{
  "attemptId": "5f3c1a9e-7b02-4d11-9c8a-6f2b0d4e1a77",
  "donorId": 90412,
  "channel": "email",
  "destination": "d***@donorpost.example",
  "codeDigest": "sha-256:4b8f2c…",
  "issuedAt": "2026-09-19T09:14:02Z",
  "expiresAt": "2026-09-19T09:19:02Z",
  "consumedAt": null,
  "failedAttempts": 1,
  "maxAttempts": 5,
  "resendCount": 1,
  "nextResendAllowedAt": "2026-09-19T09:14:32Z"
}

go deeper

for a junior

Be able to name the row and its fields out loud: what the code belongs to, when it dies, whether it has been used, how many guesses it has absorbed. Saying the server keeps a digest rather than the code is what separates a real answer from a guess.

for a middle

Explain the lifecycle: created at issue, spent on every failure, marked on success, reaped at expiry. Show where the check sits on every path that accepts a code, not just the screen the feature shipped with.

for a senior

Argue why an expired and a consumed submission cost the same as a wrong one, and what that uniformity buys against a caller who never held a code. Name which single field, removed, makes the rest decorative.

for a principal

Price the record itself: how many live attempts a donation service carries at peak, what reaping them costs, and whether the attempt state belongs in the same store as sessions given what an outage of it does to sign-in.

## Why the code cannot be the state A code delivered to a mailbox or a handset is not something the donor holds securely, and it is not something the client can be trusted to carry back. It is a **challenge the server issued**, and the only party that can say whether a submitted value is the right one, whether it is still live, and how many times it has already been guessed is the server. So a successful first factor does not produce a session; it produces a **pending sign-in attempt** — a server-side record that lives for a few minutes and then either completes or dies. Everything the second factor decides hangs off that record. A donor portal that keeps the code in the page, in a cookie, or in a field the client returns alongside its guess has moved the security decision to the side of the wire that is trying to get past it. ## The fields, and what each one is defending | Field | What it holds | What breaks without it | |---|---|---| | attempt id | the pending sign-in this code belongs to | a code minted for one attempt is redeemable in another | | code digest | a digest of the delivered code | anyone who can read the store, a backup or a stray log line can sign in during the code's life | | `expiresAt` | one absolute deadline, minutes rather than hours | the guessing window is bounded only by the donor's patience | | `consumedAt` | the instant a correct code was accepted | a correct code keeps working for the rest of its life | | `failedAttempts` / `maxAttempts` | guesses already spent against this attempt | six digits are guessed at leisure | | channel + masked destination | which address or number it went to | the portal cannot tell the donor where to look without re-deriving it | | resend cooldown | the earliest a fresh code may be requested | the reissue cycle can be driven as fast as a caller likes | ## What verification does, in order 1. **Load the pending attempt** from the server's own state, using the pre-authentication handle the client presents. No attempt, or an unknown one, is a generic failure. 2. **Check the cap before anything else.** If `failedAttempts` has reached `maxAttempts`, stop here; do not compare, do not reveal that a live code exists. 3. **Spend one attempt.** Increment the counter on every path that is not a success — wrong value, expired value, already-consumed value alike. 4. **Compare digests** and check `expiresAt` and `consumedAt` in the same pass. 5. **On success, set `consumedAt`** and hand the completed attempt on. The record is kept, spent, until its expiry passes so that a replay meets a definite refusal rather than a missing row. ## Why every outcome spends an attempt - If only a wrong value costs an attempt, an expired or consumed value becomes a free probe that says *something was here*. - Uniform cost keeps the three failures indistinguishable to a caller who never held a code, which is the whole point of answering them identically. - It makes the counter mean one simple thing — **submissions against this attempt** — instead of a sub-classification a reader has to reconstruct. - The cost to an honest donor is bounded: the cap is per pending attempt, and a fresh sign-in re-proves the first factor. ## What this record deliberately does not own - **Where the digits come from.** The generator and how many bits sit behind the code belong to the randomness subject, not here. - **How the message reaches the donor.** Queueing, priority, provider failover, retry and the rule that an already-expired code is dropped rather than re-delivered belong to the delivery design. - **The handle itself.** The identifier that carries a half-finished sign-in from the first factor to the second is the pre-login state subject; this record simply hangs off it. - **What happens after success.** Re-issuing the session identifier once the code verifies is the session lifecycle's business. ## What good looks like One row, created at issue, reaped at expiry, holding a digest and four small facts: what it is for, when it dies, whether it has been used, and how many guesses it has absorbed. A reviewer can read that row and answer *how many guesses does an attacker get, against how many accepted values, for how long* without opening any other file. If those three numbers are not all visible in the record, the design is not finished.

  • Why hold a digest of the delivered code rather than the code itself?
    Because for the minutes it is live that value is a working credential. Anyone who can read the row — an operator with query access, a backup, a log line that serialised the record — could otherwise finish the donor's sign-in. A digest lets verification compare without the store ever holding something usable, and it costs nothing, because the server never needs to show the code again after delivery.
  • Should the record be deleted the moment a correct code is accepted?
    No. Mark it consumed and keep it until its expiry passes. A replay of the accepted code then meets a definite refusal on a row that says it is spent, rather than a missing row that has to be answered by guessing what it meant. Reaping on expiry also keeps the failed-guess count around for as long as the attempt it describes could still matter.
  • Does this record belong inside the donor's authenticated session?
    There is no authenticated session yet — that is the point of a pending attempt. It hangs off the pre-authentication handle issued when the first factor succeeded, and it is discarded or promoted when the second factor resolves. The handle's own design, and the re-issuing of the session identifier after success, are separate subjects.

saying these in an interview costs you the question

  • Thinks the digits are the control, so only the code's length matters
  • Keeps the guess count in the page or in a cookie the caller returns
  • Never marks a correct code consumed, so it replays until expiry
  • Increments the counter only on a wrong value, not on an expired one
  • Stores the delivered code in the clear because it lives only minutes
  • Says the code needs no expiry because the donor will use it straight away
open as a page

Why is an authenticator app's shared secret stored in recoverable encrypted form rather than hashed like a password?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A time-derived code is recomputed by the server from the shared secret and the clock, so it must hold that secret itself, not a digest. Encrypting it at rest protects it from a database read, and nothing more.

open as a page

Why must your server generate a passkey ceremony's challenge itself and accept each one only once?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A passkey challenge is what makes each ceremony unrepeatable. The server generates it randomly, holds it against one pending ceremony and spends it on use, so a captured response cannot be replayed. The specification requires unguessable entropy and recommends at least 16 bytes.

open as a page

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%

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.

open as a page

A donor portal leaves every sign-in code it has sent valid until expiry. What does that cost the attempt cap?

level: middleimportance: must knowfreq 55%

basics

~20 s

Every simultaneously valid code multiplies an attacker's odds per guess. A cap of five guesses against one live six-digit code is about five chances in a million; twelve live codes make the same cap worth sixty in a million.

open as a page

A signed-in member changes their own password — why demand the current one, and where do the password rules belong?

level: middleimportance: must knowfreq 62%

basics

~20 s

Re-entering the current password proves knowledge of the exact credential being replaced — something an unattended signed-in browser cannot supply. The rules validating the new value belong in one shared routine behind every write path, not on the change form.

open as a page

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

level: middleimportance: must knowfreq 65%

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.

open as a page

When a step-up challenge succeeds, what does an application's own session record store to mark that elevation, and how does it fall away?

level: middleimportance: must knowfreq 52%

basics

~20 s

A server-side elevation record hung off the session: which operation it covers, the instant the challenge passed, which challenge attempt satisfied it, and its state. It is never client-asserted, and it falls away by being spent on that operation or by ageing out.

open as a page

At authenticator enrolment, what does the server generate, show once, and require before it commits the enrolment?

level: middleimportance: must knowfreq 55%

basics

~20 s

The server draws a fresh shared secret, shows it exactly once as a scannable image and as typeable text, holds it in a pending enrolment row, and activates it only after one correct code proves the authenticator really has it.

open as a page

When a passkey assertion arrives, which checks must your server run and what is each one for?

level: middleimportance: must knowfreq 75%

basics

~20 s

A passkey assertion is verified by comparison: resolve the credential, confirm the ceremony type is webauthn.get, match the challenge you issued, check the origin is expected, check rpIdHash is the SHA-256 of your rpId, check the flags, then verify the signature.

open as a page

At passkey registration, what does your server generate before the ceremony and store after it?

level: middleimportance: must knowfreq 70%

basics

~20 s

Passkey registration begins with server-generated options - a fresh random challenge, the relying-party identifier, an opaque user handle and an exclude list - and ends with one credential record per credential: credential id, public key, sign counter, user handle and transports.

open as a page

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%

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.

open as a page

What does a server store when it issues a password-reset token, and what does the emailed link carry?

level: juniorimportance: should knowfreq 60%

basics

~20 s

The reset row stores a hash of the token, the account it belongs to, an expiry the server compares, and a consumed marker. The raw token exists once, inside the link that is sent, and is never written down.

open as a page

How should a service issue, store and consume the backup codes handed out alongside an authenticator enrolment?

level: middleimportance: should knowfreq 40%

basics

~20 s

Issue them as a set, once, at activation; store each one-way, because the server only ever checks a typed one and never reproduces it; mark each used when consumed; and regenerate the whole set rather than topping it up.

open as a page

Once an authenticator code verifies, what must the server record so the same code cannot be reused?

level: middleimportance: should knowfreq 44%

basics

~20 s

Record which time step the submission matched, on the enrolment row, and refuse anything matching that step or earlier. The code stays valid for the rest of its step; only that record stops a second use.

open as a page

A donor presses resend twice on the sign-in code screen. Which counter must the server refuse to reset, and why?

level: seniorimportance: should knowfreq 48%

basics

~20 s

The failed-verification count on the pending attempt. A resend may replace the outstanding code and start a fresh expiry, but if it clears the guess count, an attacker alternates resend and guess and the cap bounds nothing.

open as a page

A federation officer sets a member's password before a congress — what must that path do that a self-service change need not?

level: seniorimportance: should knowfreq 42%

basics

~20 s

An officer-initiated set cannot re-prove a credential nobody present knows, so it substitutes an authority check over actor and subject, records both on the change, and stores a handover value carrying a must-change marker and a deadline.

open as a page

What should a failed-sign-in counter be keyed on, and how does keying it on the account alone become a weapon?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Key the refusing counter on the account-and-source pair, and keep per-source and per-account counts for shape only. A counter keyed on the account alone lets anyone who knows a member's identifier keep that member out on demand.

open as a page

A long callsign-transfer form is in flight when the step-up challenge fires: how do you resume it without re-executing the request?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Park the parsed command server-side as a single-use suspension record bound to the session, the pending challenge attempt and a short lifetime, and redirect with an opaque ticket only. On return, consume the suspension atomically, re-validate, then execute once.

open as a page

For authenticator codes, how many time steps of clock drift should a verifier accept, and what does each extra step cost?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Accept as few as real handsets require - RFC 6238 Section 6 RECOMMENDS at most one time step of delay. Each extra accepted step adds another valid code, so the window and the attempt cap are one decision rather than two.

open as a page

With an empty allowCredentials list, how does your server work out which account is signing in?

level: seniorimportance: should knowfreq 52%

basics

~20 s

An empty allowCredentials list means the server has not identified anyone yet. The authenticator offers a discoverable credential it holds for that rpId, and the assertion comes back carrying a userHandle the server matches to the account it issued that handle to.

open as a page

An assertion arrives whose signCount is lower than the value you stored - what do you actually do?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A signCount regression is a signal that two copies of the private key may exist - never a verdict, and never an identification of which copy is which. Incrementing is only a recommendation on authenticators, so the specification leaves the response to the relying party.

open as a page

A risk service answers allow, challenge or deny on each sign-in — what must your authentication path do with each answer?

level: principalimportance: should knowfreq 32%

basics

~20 s

Map each answer to a sign-in outcome: allow continues the ordinary path, challenge demands a second factor, deny ends the attempt with a recorded reason. Silence is a fourth named outcome, defaulting to the challenge.

open as a page

Should your relying party request attestation at passkey registration, and what does verifying it buy?

level: principalimportance: nice to knowfreq 34%

basics

~20 s

Attestation is evidence about the authenticator's model, not about the person. It is worth requesting only where a model policy exists and someone owns the trust anchors; otherwise none is the honest default, and the client may then zero the aaguid entirely.

open as a page