Identityauthenticationlifecyclerecoveryenumerationstep-uprevocation

Authentication as a Lifecycle

Authentication is not a login form: it is registration, credential storage, login, second factors, session establishment, re-authentication, recovery, device management and revocation — and attackers reliably target the least-defended stage, which is almost always recovery.

▶ Run the labFollow the failure

Frame the problem

Security starts with a concrete asset, attacker capability and trust crossing.

Asset
The binding between a human being and an account, and every capability that account holds.
Attacker & capability
Someone who wants to *be* a specific user, or any user. They will take the cheapest stage: a reused password, a recovery flow, a support agent, or a session token.
Trust boundary
The boundary between anonymous and identified — crossed at login, and crossed again, less carefully, at every recovery and re-authentication path.
AssetThreatAttack SurfaceTrust BoundaryVulnerabilityExploit PathImpactMitigationDefense in DepthResidual Risk

Nine stages, and where each one fails

Teams design the login form carefully and inherit the rest. The result is systems with excellent password hashing and a password reset flow that emails a token valid for seven days, or with hardware-key MFA and a support process that disables it on request.

Model the whole lifecycle and ask the same questions at each stage: what credential is presented, what does success grant, how long does it last, who can trigger it, and what does the attacker gain by defeating exactly this stage?

  • Registration — creating the account. Fails to: user enumeration ("that email is taken"), automated bulk signup, unverified email letting someone claim an address they do not own.
  • Credential storage — how the secret is kept. Fails to: fast hashes, missing salts, plaintext in logs. See Password Storage.
  • Login — presenting the credential. Fails to: credential stuffing, brute force, timing differences that reveal whether an account exists.
  • Second factor — proving possession. Fails to: SMS interception, real-time phishing proxies, push-notification fatigue.
  • Session establishment — turning a successful login into ongoing access. Fails to: session fixation, tokens in URLs, missing cookie attributes. See Sessions.
  • Re-authentication (step-up) — proving it is still you before something dangerous. Usually absent entirely, which is why a stolen session can change the email address.
  • Recovery — the path for users who lost the credential. Fails to: everything. This is reliably the weakest stage.
  • Device and session management — what the user can see and revoke. Fails to: no visibility, no bulk revoke, sessions that survive a password change.
  • Deprovisioning — ending access. Fails to: SSO disabled but API tokens still valid, OAuth grants that outlive the account.
The lifecycle, with the two stages attackers prefer
bypasses login + MFARegisterRecovery (weakest)Store credentialLoginSecond factorSessionStep-up for risky actionsRevoke / deprovision
UserLLMAgentToolDataDecisionHumanGuardrail

Recovery is an alternate login with weaker controls

Every account recovery flow is, by construction, a way to obtain access without the credential. If it is weaker than the login it replaces — and it usually is — then it *is* the authentication strength of the system, regardless of what the login does.

The common failures are consistent: reset tokens that are long-lived, reusable, or predictable; reset links that do not invalidate on use or on a subsequent password change; recovery that skips MFA entirely, so a hardware key protects nothing; security questions whose answers are public facts; and a support process where a convincing phone call is sufficient.

A defensible recovery flow treats itself as a high-risk operation: a single-use token with a lifetime measured in minutes, bound to the account and invalidated by any subsequent credential change; MFA still required if MFA is enrolled; all existing sessions revoked on completion; and a notification to the previously-known contact that cannot be suppressed by the person doing the reset. Where the account is high value, add a delay with a cancel link — 24 hours during which the real owner can stop it — which converts a silent takeover into a race the defender can win.

1async function requestReset(email: string) {
2 const user = await users.byEmail(email)
3 // Always respond identically and in similar time, whether or not the account exists.
4 if (user) {
5 const raw = crypto.randomBytes(32).toString('base64url') // 256 bits of entropy
6 await resets.insert({
7 userId: user.id,
8 tokenHash: sha256(raw), // store the hash: a leaked table must not grant resets
9 expiresAt: Date.now() + 15 * 60_000,
10 usedAt: null,
11 requestedFromIp: currentIp(),
12 })
13 await mail.send(user.email, resetLink(raw))
14 }
15 return { ok: true, message: 'If that address has an account, we sent a link.' }
16}
17
18async function completeReset(raw: string, newPassword: string) {
19 const row = await resets.byTokenHash(sha256(raw))
20 if (!row || row.usedAt || row.expiresAt < Date.now()) throw new InvalidReset()
21
22 await db.transaction(async (tx) => {
23 await tx.resets.markUsed(row.id) // single use
24 await tx.resets.invalidateAllFor(row.userId) // sibling tokens die too
25 await tx.users.setPassword(row.userId, await hashPassword(newPassword))
26 await tx.sessions.revokeAll(row.userId) // every existing session ends
27 })
28 // MFA enrolment is NOT cleared here: a reset proves email control, not possession.
29 await mail.send(await emailFor(row.userId), 'Your password was changed')
30 await audit.log('password.reset.completed', { userId: row.userId, ip: currentIp() })
31}

Enumeration, step-up and revocation

User enumeration is the quiet one. If registration says "email already in use", login says "no such user" for one address and "wrong password" for another, or reset takes 40 ms for unknown accounts and 300 ms for known ones, an attacker can build a list of your real users before trying a single password. That list makes credential stuffing dramatically more efficient. The defence is uniform responses and uniform timing on all three flows, which usually costs some user-experience clarity — a real trade worth making for accounts where membership itself is sensitive, and worth reconsidering for a consumer product where it mostly costs support tickets.

Step-up authentication is the control that limits what a stolen session can do. Changing the email address, adding a payment method, disabling MFA, creating an API token and adding an OAuth application are all account-takeover primitives, and each should require re-presenting a credential regardless of session age. This is the single cheapest defence against session theft, and it is missing from most applications.

Revocation is what turns a compromise into an incident with an end. The user should be able to see active sessions with device and location, revoke one or all, and have that take effect immediately rather than at token expiry. A password change should revoke every other session by default. If your token design cannot do this — see JWT Failure Modes — that is a design consequence you chose, and it should be a conscious one.

Key points

  • Authentication is nine stages, and attackers pick the weakest — which is almost always recovery.
  • A recovery flow weaker than login *is* your authentication strength; make it single-use, short-lived, MFA-preserving and session-revoking.
  • Uniform responses and timing on registration, login and reset prevent building a list of your real users.
  • Step-up re-authentication before account-takeover primitives is the cheapest defence against session theft.
  • Revocation must be immediate and user-visible; a design that cannot revoke has chosen that, and should have chosen it deliberately.

Boundary control exercise

This lesson uses the shared boundary-control exercise.

Boundary control check
Untrusted input / identity
Trust boundary
Privileged asset
Prevention may fail silently.

Follow the attack

Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.

  1. 1
    Attacker → enumerate: use registration, login and reset responses to confirm which addresses have accounts.
  2. 2
    Confirmed accounts → credential stuffing with passwords from unrelated breaches.
  3. 3
    Failure → recovery: trigger a password reset and attack the token, the email account, or the support process instead.
  4. 4
    Access → persistence: add an API token, add an OAuth app, or change the recovery email so that fixing the password changes nothing.
Blast radius
  • Full control of the account and everything it can reach, including data belonging to others where the account has sharing or admin rights.
  • Persistence beyond remediation if the attacker created a second credential before being detected.
  • For an admin account, the blast radius is the whole tenant or the whole system.

Defend, detect, recover

One prevention is a single point of security failure. Layer it and make failure observable.

Prevent
  • • Use a well-maintained identity provider or library for the credential and factor handling; build only the parts specific to your domain.
  • • Harden recovery to the same standard as login, including retaining MFA requirements.
  • • Require step-up authentication for every account-takeover primitive.
  • • Revoke all sessions and API tokens on credential change; notify the user through a channel the attacker has not just changed.
Detect
  • • Alert on bursts of failed logins across many accounts from few sources (stuffing) and many attempts on one account (targeted).
  • • Alert on recovery requests at unusual rates, and on any recovery completed from a device and location never seen for that account.
  • • Alert on the persistence primitives: new API token, new OAuth grant, changed recovery email, disabled MFA.
Respond & recover
  • • Revoke every session and token for the account, then force credential re-establishment through a verified channel.
  • • Check for persistence added during the compromise window before declaring the account clean.
  • • Notify the user out of band, since in-band channels may now be attacker-controlled.
Residual risk
  • • A compromised email account defeats most recovery designs, because email is the root of the recovery tree for nearly everything.
  • • Support processes remain a human-judgment path and are the branch attackers move to when the technical ones close.
  • • Users reuse passwords across services no matter what you do; assume the credential is known and design accordingly.

Misconceptions

Claim
“We require MFA, so account takeover is handled.”
Reality
Only if MFA is also required on recovery, on step-up, and cannot be removed by the support process. MFA that recovery bypasses protects the login form and nothing else.
Claim
“Password complexity rules improve security.”
Reality
Length and breached-password checks help; composition rules mainly push users toward predictable substitutions and password reuse. Modern guidance favours long passphrases, a breach corpus check, and MFA.