Chapter 46 — Authentication, sessions, and federated identity
In one sentence
Authentication proves who is making a request; the hard parts are storing credentials safely (done in Ch 10), managing sessions, resisting phishing, and — when you delegate to Google/Okta/etc. — getting OAuth 2.0 and OIDC exactly right.
Sessions: the two models
SERVER-SIDE SESSIONS (stateful)
cookie = opaque random 128-bit session ID -> server looks up session
data in a store (Redis, DB).
+ trivial to revoke (delete the row), rotate, inspect
+ no sensitive data on the client
- needs a shared/replicated store
STATELESS TOKENS (JWT et al.)
cookie/header = signed token containing claims (sub, exp, roles)
+ no server lookup; scales across services
- REVOCATION IS HARD (token is valid until exp regardless)
- footgun-rich (see below)
- claims are readable by the client (Ch 6) -- never put secrets in
Common middle ground: short-lived JWT access token (5-15 min) +
long-lived opaque refresh token stored server-side and revocable.
Session hygiene, both models:
[ ] Generate IDs with a CSPRNG, >=128 bits.
[ ] Set Secure; HttpOnly; SameSite=Lax|Strict; __Host- prefix.
[ ] ROTATE the session ID on privilege change (login, step-up) ->
defeats session fixation.
[ ] Idle timeout + absolute timeout. Invalidate server-side on logout.
[ ] Bind minimally to context (e.g. re-auth on IP/UA change for
high-value) -- carefully, mobile users roam.
[ ] One place to list & revoke a user's active sessions.
JWT: the specific footguns
alg:none -> some libraries accepted an unsigned token as valid.
Pin the expected algorithm; reject 'none'.
alg confusion -> token says RS256 but server verifies with the RSA
(RS256->HS256) PUBLIC key as an HMAC secret -> attacker forges
tokens using the public key. Pin alg; use separate
verify APIs.
weak HMAC secret -> brute-forced offline (jwt_tool, hashcat). Use a
long random secret / proper keys.
no exp / long exp / no aud / no iss checks -> replay, token reuse
across services. Validate exp, nbf, iss, aud, sub.
kid injection -> path traversal / SQLi via the key-id header.
storing in localStorage -> XSS reads it. Prefer a cookie with the
flags above; if you must use headers, keep tokens
short-lived.
can't revoke -> keep them short; maintain a denylist / token
version / "tokens issued before T are invalid".
Passwords and MFA (building on Ch 10)
- Argon2id storage (Ch 10). Screen against Have I Been Pwned. No
forced composition or rotation (NIST SP 800-63B).
- Rate-limit + lockout-with-care (per-account and per-IP; avoid
enabling DoS-by-lockout; use exponential backoff + CAPTCHA-as-late-
resort + device signals).
- Uniform responses/timing for "no such user" vs "wrong password"
(anti-enumeration, Ch 10).
- MFA tiers, weakest to strongest:
SMS OTP -> phishable, SIM-swappable. Better than nothing.
TOTP (authenticator app) -> phishable (real-time relay) but no
SIM-swap. Good baseline.
Push w/ number-matching -> resists MFA fatigue.
WebAuthn / PASSKEYS (FIDO2) -> PHISHING-RESISTANT: the credential
is bound to the origin, cryptographic
challenge-response, private key in a
TPM/secure enclave/security key.
THE target to move users toward.
- Account recovery is the real backdoor. "Reset via email" makes email
the true root of trust. Recovery must be as strong as primary auth
(or explicitly, deliberately weaker with monitoring).
OAuth 2.0 and OIDC — the 90-second model
OAuth 2.0 = DELEGATED AUTHORIZATION ("let this app access my
Google Calendar"). It is NOT an authentication protocol.
OIDC (OpenID Connect) = an identity layer ON TOP of OAuth 2.0
("log in with Google") -- adds the ID TOKEN (a JWT about the user)
and a /userinfo endpoint.
ROLES: Resource Owner (user) | Client (your app) |
Authorization Server (Google/Okta) | Resource Server (the API)
USE THE AUTHORIZATION CODE FLOW + PKCE, always (SPAs and mobile
included). The old Implicit and Password (ROPC) grants are deprecated.
1. app redirects user to the Authorization Server with:
response_type=code, client_id, redirect_uri, scope,
state=<random, CSRF>, code_challenge=<S256(verifier)>
2. user authenticates + consents there
3. AS redirects back: ?code=...&state=...
app checks state matches.
4. app -> AS /token (back-channel): code + code_verifier + client
creds -> access_token (+ id_token for OIDC, + refresh_token)
5. app calls the API with Authorization: Bearer <access_token>
OAuth/OIDC mistakes that cause account takeover:
- Not validating `state` -> CSRF on the callback / code injection.
- Loose redirect_uri matching (prefix/substring/open-redirect on the
client) -> code/token exfiltration. Require EXACT registered URIs.
- No PKCE -> authorization-code interception (esp. mobile/SPA).
- Accepting an id_token without verifying signature, iss, aud, exp,
nonce.
- Using the ACCESS token as proof of identity (it's for the API, may be
opaque, has no audience for your app) -> use the ID token / userinfo.
- "Confused deputy": trusting an id_token minted for a DIFFERENT client
(check `aud`).
- Mixing up auth/authz: OIDC tells you WHO; your app still decides
WHAT they can do (Ch 45).
- Long-lived tokens in the browser; refresh tokens without rotation +
reuse detection.
Practice (45 min) — lab only
1. Session fixation: in a lab app, capture your pre-login session ID,
log in, check whether it CHANGED. If not, that's the bug -- fix by
regenerating on login.
2. JWT:
- Decode a lab JWT on jwt.io. Flip a claim, re-encode with alg:none
or with 'HS256' + the RS256 public key. Try it against the app
(Juice Shop has a JWT challenge).
- Fix a local verifier: pin alg, verify aud/iss/exp, reject none.
- Crack a weak-secret HS256 token with `jwt_tool` / hashcat -m 16500
against a wordlist (your token only).
3. OAuth: run a local OIDC provider (Keycloak / Dex / `oauth2-proxy`
with a test IdP). Implement the auth-code + PKCE flow in ~50 lines.
Then break it: skip `state` and show the callback is now CSRF-able;
loosen `redirect_uri` matching and show code theft via an open
redirect.
4. Add WebAuthn to a toy login (SimpleWebAuthn library). Register a
passkey (platform authenticator or a security key) and log in with
it. Note there is no shared secret to phish.
Common confusions
- "JWT = secure sessions." JWTs trade revocability and simplicity for scale, and carry a list of well-known footguns. For a single app, opaque server-side sessions are simpler and safer.
- "OAuth logs users in." OAuth authorizes access to resources. OIDC does login. Using bare OAuth for login (and trusting the access token as identity) causes real account-takeover bugs.
- "PKCE is only for mobile." Current guidance: PKCE for all clients, including server-side and SPA.
- "MFA stops phishing." SMS/TOTP/push are relay-phishable. Only WebAuthn/passkeys are phishing-resistant (origin-bound).
- "Lock the account after N failures." Naive lockout is a DoS vector (lock everyone out by trying their emails). Use backoff, IP/device signals, and targeted friction.
- "Store the JWT in localStorage for SPAs." Then any XSS exfiltrates it.
Cookie +
HttpOnly+SameSite+ short lifetime is usually better.
Check yourself
- Stateful sessions vs stateless JWTs — one advantage and one drawback of each.
- What is session fixation and the one-line fix?
- Name three JWT footguns and the mitigation for each.
- OAuth 2.0 vs OIDC — which authenticates the user? Which flow should you use, and what does PKCE prevent?
- Why is WebAuthn "phishing-resistant" when TOTP is not?
- Why is account recovery often the weakest link, and what's the standard for it?
Further reading
- Standard: NIST SP 800-63B (authentication) — sections 5 (authenticators) and 7 (session management).
- Spec/guidance: OAuth 2.0 Security Best Current Practice (RFC 9700), OAuth
2.1 draft, and
oauth.netdiagrams.openid.netfor OIDC. - Cheat sheets: OWASP "Authentication", "Session Management", "JSON Web Token for Java" (concepts apply broadly), "Credential Stuffing Prevention".
- Site:
webauthn.guideandpasskeys.dev— clear WebAuthn/passkey explainers.jwt.iofor decoding. - Course: PortSwigger Academy — "Authentication", "JWT attacks", "OAuth 2.0 authentication vulnerabilities" tracks.
- Tool:
jwt_tool(github.com/ticarpi/jwt_tool) — JWT attack toolkit, lab use.