Login gets all the attention, but the flows around it — verify your email, reset a forgotten password, recover a lost device — are where real apps quietly leak. They're "boring," so tutorials skip them and bugs pile up. Good news: they're all the same single-use-token machinery from Lesson 8. The skill today is spotting the handful of footguns that turn a routine reset link into an account-takeover.
🎯 Mission: a mental model of modern auth + the ability to wire it into a real appEmail verification, password reset, and recovery are one pattern — a single-use, expiring, hashed token mailed to the user — and their security lives entirely in four footguns. Don't enumerate users, make tokens single-use and short-lived, store only the hash, and invalidate all sessions when the password changes. A password reset is a back door into the account; if it's weaker than your login, attackers walk through it instead.
A password reset is, by design, "let someone who lost their credential get a new one." That's also exactly what an attacker wants. So the reset flow is a parallel authentication path — and an attacker will always probe the weakest path, not the front door. Spend your login hardening on a strong password hash and MFA, then leave reset wide open, and none of it mattered.
Five steps. Note how much is about what you don't reveal and what you clean up afterward:
sha256(token), never the token. Then a database leak can't be turned into account takeovers — the raw token lives only in the email. (Same as Lessons 4 & 8.)A zero-dependency lab runs the secure reset and proves each footgun is closed — identical replies, single-use, expiry, hashed storage, session eviction.
# in your terminal (no npm install needed):
cd ~/projects/learn/public/courses/auth/practice/account-lifecycle-lab
node demo.js
FOOTGUN 1 — real vs fake email both return: "If an account exists for that address, we sent a reset link." (identical) RESET: password updated; ALSO invalidated 2 existing sessions FOOTGUN 2 — same link twice → 🛡️ "token already used" FOOTGUN 3 — clicked after 21 min → 🛡️ "token expired" BONUS — DB stores only a hash → a leak can't produce the email's raw tokenYou watched a reset flow that reveals nothing, can't be replayed, can't be staled, survives a DB leak, and evicts a riding attacker. That's five separate account-takeover paths closed in one flow. Open
demo.js — notice requestReset() returns the same string on both branches; that single decision is the whole anti-enumeration defense.On signup, email a single-use expiring link; clicking it flips email_verified = true. Identical machinery to reset — same four properties — the only difference is what the click does (set a flag, not change a credential). Verifying email matters because so much else (magic links, reset) trusts that the address truly belongs to the user.
Recovery is "the user lost their factor entirely" (forgot password and lost their phone/passkey). It's genuinely hard because your recovery path is your real security floor — an attacker attacks recovery, not the strong factor. Options, roughly strongest-to-weakest:
| Recovery method | Strength | Note |
|---|---|---|
| A second registered passkey / device | Strong | Encourage users to enroll two from the start |
| One-time backup codes (shown at MFA enrollment) | Strong | User stores them offline; each single-use |
| Email reset link | Medium | As strong as the user's inbox |
| SMS / security questions | Weak | SIM-swap & researchable answers — avoid as sole path |
Q1. Your "forgot password" page says "no account with that email" for unknown addresses. Why is that a problem here specifically?
It's a user-enumeration oracle (same issue as login in Lesson 4, but reset pages are often forgotten). An attacker submits many addresses and learns which are registered, then targets them for phishing/credential-stuffing. Fix: return an identical message ("if an account exists, we sent a link") in roughly constant time, regardless of existence. (§03 footgun 1.)
Q2. Why must a password reset invalidate all existing sessions, when the user just proved control of their email?
Because the reason for the reset might be that an attacker already has a stolen session. Changing the password without killing sessions changes the lock but leaves the intruder inside. Invalidating all sessions (Lesson 7's family revocation) evicts any rider, forcing fresh authentication everywhere. (§03 footgun 4.)
Q3. You deployed passkeys for login but recovery is "answer two security questions." What's the real security level of the account, and why?
Roughly the strength of the security questions — i.e. weak. Attackers go after the easiest path, and recovery is a parallel authentication route into the same account. Phishing-proof passkeys don't matter if recovery can be socially engineered. Recovery must match the strength of the primary factor (e.g. a second passkey or offline backup codes). (§05.)
You now have every piece a real app needs — login methods, sessions/tokens, MFA, and the lifecycle flows around them. Lesson 12 is the capstone: a decision framework for choosing which of these to combine for a given app, and a complete, running Express auth app that wires the pieces together end to end — signup, hashed passwords, sessions, CSRF, and a protected route you can actually hit. The map becomes a build.
💬 Ask me anything — e.g. "wire the reset flow with real email + tokens in Express", "design backup codes for the TOTP lab", or "how do big apps handle account recovery for passkeys?" · Back to Lesson 10 · Lesson 8 · Resources.