Establishing who the user is, before any authorisation happens. The complement to the OAuth/OIDC series.
Hash · Verify · Strengthen · Replace
01
Topics
Passwords (and how to stop using them)
Hashing — bcrypt, Argon2id, scrypt, PBKDF2
Password policies that actually help
Breach-detection (HIBP) and credential stuffing
Account-lockout, rate-limiting, abuse signals
Multi-factor authentication
The three factor classes — what each is good for
TOTP (RFC 6238) and HOTP (RFC 4226) in detail
SMS / email codes — and why they're the weakest
Push, hardware OTP, smart cards
Phishing-resistant — WebAuthn / FIDO2 / Passkeys
The three names for the same thing
Authenticator types — platform vs roaming
Discoverable credentials & conditional UI
Synced passkeys vs device-bound
Attestation, AAGUIDs, FIDO MDS
Operational
Account recovery — the unsolved problem
Risk-based / adaptive auth
Step-up & CAEP shared signals
Anti-bot & abuse detection
Choosing a stack
02
Authentication vs Authorisation — Where This Deck Sits
The OAuth/OIDC decks in this series cover authorisation ("what may an app do on a user's behalf?") and identity assertions ("a JWT saying this is Alice"). Neither tells you how the user proved they were Alice in the first place. That is this deck.
A working definition
Authentication is the process of getting a high-confidence answer to "is the entity at the other end of this connection the human (or service) we expect?". The output of authentication is what feeds the AS in OAuth and the OP in OIDC.
03
Password Hashing — The Only Acceptable Way to Store Passwords
If you ever store a password in plaintext, reverse-encrypted, or with a fast hash (MD5, SHA-256), you have lost. Use a memory-hard password-hashing function with a per-user salt and tuned cost.
Function
Year
Type
Status in 2026
Why
MD5 / SHA-1 / SHA-256 (raw)
1992+
Fast hash
Forbidden
Crackable at billions/sec on a single GPU.
PBKDF2-HMAC-SHA256
2000
Iterated hash
Acceptable for FIPS-only
CPU-only, GPU-friendly. Use ≥ 600 000 iterations (OWASP 2023).
bcrypt
1999
Adaptive
Acceptable
Memory-light (4 KB) — modern GPUs / FPGAs eat it. Use cost ≥ 12.
$argon2id$v=19$m=47104,t=1,p=1$
c2FsdHNhbHRzYWx0c2FsdA$
QthRKhMKM7wG2pfjzvVXsBcEfBLk8qUZcM…
↑ algorithm + version
↑ memory KiB · time · parallelism
↑ random salt (≥ 16 bytes, per-user)
↑ derived key
Tuning rule of thumb
Pick parameters so a single hash takes ~250–500 ms on production hardware. Update parameters as hardware improves; rehash on next successful login.
Always pepper if you can
A pepper is a server-side secret (in HSM / KMS / Vault) HMAC'd onto the password before hashing.
Stolen DB → attacker still needs the pepper to make any progress.
Trade-off: rotation is harder (need to rehash on login).
Common mistakes
Using password.equals(stored) — timing leak. Use constant_time_eq.
Truncating passwords to fit a column. Allow ≥ 64 chars.
Storing the hash and a "challenge" copy for SSO — defeats the hash.
Salting per-app instead of per-user.
04
Password Policy — What Actually Helps
Decades of "must contain a number, a symbol and the chief executive's mother's maiden name" gave us "Password1!". NIST SP 800-63B (current) and BS 8484 agree on a much shorter list.
Do
Minimum length 8 for low-risk; 12+ for normal accounts; 15+ for admin.
Allow length up to at least 64 characters; allow all printable Unicode + spaces.
Block known-breached passwords (HIBP Pwned Passwords API) — k-anonymity prefix lookup, not raw upload.
Block trivial dictionary words and the username itself.
Show the user a strength meter (zxcvbn) tied to the same logic the server uses.
Don't
Force scheduled rotation. Rotate on suspicion of compromise; otherwise leave alone.
Force composition rules (1 upper / 1 number / 1 symbol). They reduce entropy in practice.
Block paste — that's how password managers work.
Truncate or strip "special" characters silently.
Send the password back via email "for confirmation".
Server only ever sees the first 5 chars of a SHA-1 hash; the API returns ~500 candidates; you check locally.
05
Rate-Limiting, Lockout & Credential Stuffing
The single most consequential authentication API is /login. Every credential-stuffing campaign first finds it.
Three signals to rate-limit on
Per-account — exponential back-off after N failures for the same username. Protects each user.
Per-IP — caps brute-force from a single client. Easily defeated by botnets but raises cost.
Per-network / ASN — catches botnets concentrated on a hosting provider.
Lockout — the trade-off
Soft lockout — auto-unlock after N minutes. Default for consumer apps.
Hard lockout — admin re-enable. For high-value accounts; doubles as a self-serve DoS surface.
Always notify the user after the fact, never block silently.
Credential stuffing — what it actually looks like
Attacker has a list of (email, password) pairs from another site's breach.
They try each one against your /login at low rate per IP, high diversity of IPs.
Per-IP limits don't catch it. Per-account limits do — but only if you're prepared for users to be locked out by accident.
Best defence: HIBP-block known-breached passwords at signup and at login, plus risk-based MFA challenges on suspicious sessions.
Username enumeration
"Wrong password" vs "user not found" lets an attacker discover valid emails. Always return the same string and the same response time.
Same on signup ("email already registered") — use a generic "we've sent you a link" instead.
Same on password-reset.
06
The Three Factor Classes
Something you know
Password / passphrase
PIN
"Security questions"
Cheap, universal, phishable. The factor everyone has and the factor every attacker is best at extracting.
Something you have
Phone (TOTP app, push, SMS)
Hardware token (YubiKey, Titan, Feitian)
Smart card / PIV
Recovery codes printed on paper
Adds a physical constraint — attacker must possess the device. Susceptible to SIM-swap (SMS) and theft.
Something you are
Fingerprint, face, voice
Behavioural biometrics (typing rhythm)
Convenient, not a primary remote factor — biometric data leaks and you can't rotate your face. Used as a local unlock for a stronger factor.
"MFA" really means "two factors from different classes"
Password + security questions = one factor (both "know"). Password + TOTP = two factors. Password + face-unlock-of-passkey = two factors (the face unlocks the have). The class boundary, not the count, is what matters.
NIST AAL ladder (SP 800-63B)
AAL1 — single factor. Passwords alone live here.
AAL2 — two factors, one cryptographic. TOTP / push / hardware token + password.
HOTP (RFC 4226, 2005): HMAC-based one-time password from a counter. TOTP (RFC 6238, 2011): use the current Unix time / 30 s window as the counter. Both standards are open; the server and the app share a per-user secret only.
The whole algorithm
function totp(secret, t = floor(time()/30)) {
let h = hmac_sha1(secret, int_to_8bytes(t));
let off = h[19] & 0x0f;
let bin = ((h[off]&0x7f)<<24)|(h[off+1]<<16)
|(h[off+2]<<8)|h[off+3];
return (bin % 1_000_000).toString().padStart(6,'0');
}
Server checks the current + previous + next windows to allow ~30 s of clock skew.
The clientDataJSON the authenticator signs over includes the actual origin of the page making the request. A phish at acme.evil.com can never produce a signature the real acme.com server would accept — because the authenticator only ever signs for what it sees in the address bar.
10
Authenticator Types — Platform vs Roaming, Synced vs Bound
Platform
Roaming
What it is
Built into the device (Touch ID, Face ID, Windows Hello)
Apple iCloud Keychain, Google Password Manager, 1Password, Bitwarden, Microsoft (Edge / Authenticator).
Private key replicated, end-to-end encrypted with the user's account password / device PIN, across all the user's devices.
Lose your phone? Sign in on a new one with your iCloud / Google account, your passkeys are there.
The default passkey type for consumer apps in 2026.
Device-bound credentials
Stay on a single device; cannot be exported.
Hardware tokens are always device-bound. Platform authenticators can be configured this way.
Required by some regulators (eIDAS High, FIDO L3 attestation, defence).
Operationally heavier: every device needs its own enrolment, and a lost device = a recovery flow.
The synced-vs-bound debate
Consumer security teams: "synced is fine, the real risk is account takeover, and synced passkeys still beat passwords by an order of magnitude". Regulated security teams: "if it can leave the device, it doesn't satisfy hardware-bound assurance". Both are right for their threat models.
11
Discoverable Credentials & Conditional UI
The two features that make passkeys feel better than passwords, not just safer.
Discoverable credentials (a.k.a. resident keys)
Old (non-discoverable): server must hand the authenticator the credential ID first; the user must type a username.
New (discoverable): the credential carries the username with it, on the authenticator. The login page can ask "any passkey for this site?".
This is what enables username-less sign-in.
Conditional UI (autofill)
// in your login form
<input type="text" name="username"
autocomplete="username webauthn">
// JS at page load
navigator.credentials.get({
mediation: "conditional",
publicKey: { challenge, rpId, userVerification: "preferred" }
});
Browser shows passkeys in the same dropdown as saved passwords. User taps one → done. The flow that finally beats password manager autofill.
A 2026-grade login UX
Page renders an empty username field with autocomplete="username webauthn".
Conditional navigator.credentials.get() fires in the background.
If the user has a synced passkey for this site, it appears in the autofill bubble.
One tap (Touch ID / Face ID) and they're in. No username, no password, no MFA prompt.
If no passkey: form falls back to password + (TOTP / push) flow.
Best practice
Always present the passkey path and a fall-back. Don't force migration; nudge it: "We just signed you in with your password. Want to add a passkey for next time?" Conversion rates rise sharply when you ask in-context.
12
Attestation, AAGUIDs & the FIDO Metadata Service
For regulated deployments you need to know what kind of authenticator the user just enrolled — was it a YubiKey 5, a synced iCloud passkey, a software-only fake?
Attestation in one paragraph
During registration the authenticator can include a signed attestation statement proving its make/model. Attached to it is the AAGUID — a 128-bit identifier for the authenticator model.
FIDO Alliance publishes a signed JSON list of every certified authenticator AAGUID with its capabilities, certifications and revocation status.
Your RP fetches MDS regularly, indexed by AAGUID.
Lets you say "only accept FIDO L2 hardware authenticators for admin enrolment".
Attestation conveyance
attestation
Meaning
none
RP doesn't want it; default for consumer apps. Privacy-preserving.
indirect
Browser may anonymise / batch-attest. Compromise.
direct
Real attestation; required for regulated / enterprise enrolment.
enterprise
Permits per-device serial-number attestation; only allowed for whitelisted RPs.
Privacy trade-off
Direct attestation can let the RP track which physical YubiKey the user used across services. Browsers may rewrite attestation to prevent this; consumer apps should just use none.
13
Account Recovery — The Unsolved Problem
The strongest authenticator in the world is undone by the recovery flow. Whatever path the legitimate user takes when they lose access is the same path an attacker takes when they don't have any.
Common recovery paths, ranked by safety
A second registered authenticator — second YubiKey, second device's passkey. Best.
Single-use printed recovery codes, generated at enrolment, stored offline.
In-person verification at a retail counter (banks). Slow, expensive, hard to phish.
Synced-credential recovery (iCloud / Google) — strong if the underlying account is locked down.
Email + previous-password challenge — moderate.
"Identity verification" (photo of passport, selfie) — phishable; depends on a vendor's KYC quality.
Security questions — please no.
Patterns that work in practice
Encourage two passkeys at enrolment ("set up a backup now?"). Most users won't, but the prompt halves recovery volume.
Cool-down on recovery — recovered accounts spend 24–72 h in a low-trust mode (no high-value actions, alerts to the original email).
Notify aggressively — recovery initiated, recovery completed, all to every channel you have on file.
Make the recovery path obvious in the UI — hidden recovery becomes social-engineering scope for support staff.
If you do nothing else
Decide upfront whether your security model is "we never recover an account, lost = lost" (e.g. crypto wallets) or "we always recover, with friction proportional to risk" (every consumer app). Trying to do both produces the worst of each.
14
Risk-Based / Adaptive Authentication & CAEP
Risk-based auth — the idea
Don't ask the user for MFA on every login. Score each session for risk and step up only when the score warrants.
New device / fingerprint?
New IP geolocation, ASN, or impossible-travel from the previous session?
Time of day vs the user's pattern?
Browser / OS user-agent shift?
Logins for many distinct accounts from the same IP (credential stuffing)?
HIBP-flagged password just attempted (yes, even if correct)?
Step-up — what to ask for
Low risk: nothing.
Medium: extra factor for this session only.
High: re-auth + email confirmation; lock account on second failure.
If you're using OIDC, request the step-up via acr_values + prompt=login; verify the returned acr and amr.
CAEP & SSF — Continuous Access Evaluation
SSF = Shared Signals Framework (OpenID Foundation): a publish/subscribe channel for security events between IdPs and SPs.
CAEP = the profile of SSF for "this user just changed their password / had their session revoked / failed step-up".
Lets an SP react to a signal from another SP / IdP during a session, not only at login.
Already deployed by Google, Microsoft, Okta, Cisco; growing in 2025–2026.
Practical pattern
Subscribe your high-value SaaS apps to the workforce IdP's CAEP stream. When the IdP sees a suspicious sign-in, fires token-claims-change or session-revoked; every downstream app drops the session within seconds, not hours.
15
Anti-Bot & Abuse Detection
Authentication endpoints are also the primary surface for non-credential attacks: account creation abuse, scraping, low-and-slow stuffing.