Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authentication: Identity, Tokens, Sessions, and Trust

Overview

Authentication in production is not one mechanism but a pipeline of five: OAuth2 flows that delegate authorization between parties, tokens (JWTs) that carry claims, sessions that remember state, cookies that bind state to a browser, and SSO protocols (SAML, OIDC, Kerberos) that propagate trust from one identity provider to many relying parties. Each subsystem was designed to solve a different problem — OAuth2 for third-party delegation without password sharing, JWTs for verification without a database lookup, sessions for server-side control, cookies for transport — and every real-world auth system is a negotiated blend whose correctness depends on where the state actually lives and how long each layer is allowed to believe something.

That last phrase is the connection this page maps. The industry’s most repeated auth claim — “we use JWTs, so we are stateless” — is the entry point, because tracing it forces you through every joint in the pipeline: a JWT moves the lookup state out of the server and into the token, but it simultaneously creates key-distribution state, revocation state (unless you accept “the token lives until exp no matter what”), and clock dependencies (exp/nbf are evaluated against each verifier’s local clock). Auth failures propagate in exactly these three channels: tokens that outlive the authority that issued them, keys that rotate faster than verifiers refresh, and clocks that drift between issuer and verifier. Senior interviewers probe precisely here — not “what is a JWT” but “how do you revoke one, and what does that cost you?” — because the answer reveals whether the candidate sees auth as a pipeline with bounded staleness or a checklist of acronyms.

The Subsystems, Independently

OAuth2 (RFC 6749): delegation, not authentication. OAuth2 defines roles — resource owner, client, authorization server, resource server — and grant flows for the client to obtain an access token. The authorization-code flow (with PKCE for public clients) is the workhorse: the user authenticates at the authorization server, the client receives a short-lived code via redirect, and exchanges it (with client credentials) for tokens — the access token never transits the browser. Client-credentials flow serves machine-to-machine callers. The classic exam trap: OAuth2 issues access tokens; it does not define user authentication — OIDC exists precisely to add that layer. See ../../security/oauth2-internals.md.

JWT (RFC 7519): claims with an envelope. A JWT is three base64url parts — header, payload, signature — signed (JWS) with HMAC, RSA, ECDSA, or EdDSA; signatures make claims tamper-evident, not secret (encryption is a separate JWE envelope). Standard claims do the temporal work: iss, aud, sub, iat, nbf, exp, jti. Verification is local and cheap — check signature against a known public key, check time bounds, check audience — with no per-request store hit. That efficiency is the whole appeal and the whole trap: verification without state is only possible because the issuer pre-commits to the claim set for the token’s lifetime. See ../../security/jwt-internals.md.

Sessions and cookies: state with an address. A classic session is a random ID in a Set-Cookie header (RFC 6265; HttpOnly against script theft, Secure against plaintext, SameSite against CSRF) mapped server-side to a record in a shared store — Redis or a session database — holding identity, roles, and expiry. Sliding expirations extend lifetime on activity; revocation is a key delete. Sessions scale horizontally only via a shared store or sticky routing, which is exactly the tradeoff the Why-Not Handbook frames as stateless-versus-stateful (../why-not-handbook/stateless-vs-stateful.md). Cookies are also where XSS/CSRF battles happen, which is why bearer tokens in Authorization headers and cookie sessions coexist: different threat models, different transports (see ../../networks/http/https.md and ../../security/authentication.md).

Refresh tokens and revocation. Because access tokens are short-lived (minutes), a longer-lived refresh token lets the client obtain new access tokens without re-prompting the user. The refresh token is the stateful half of the pair: it lives server-side (or as a rotatable, stored credential), it can be revoked, and its rotation on each use detects theft. Revocation is where every stateless claim goes to die: a denylist of jtis until exp is a database again; a key rotation invalidates everything at once (blunt); short exp is “revocation by timer” with a bounded but nonzero window of post-logout validity.

SSO trust: SAML, OIDC, Kerberos. Enterprise SSO (SAML 2.0 assertions, XML-signed; see ../../security/saml.md) and OIDC (a JWT identity layer over OAuth2; see ../../security/oidc.md) both implement the same structure: an identity provider signs a statement, relying parties verify against published keys (SAML metadata, OIDC JWKS endpoints) fetched and cached out of band. Kerberos does it with symmetric tickets and a KDC — and an explicit, documented clock-skew tolerance (commonly five minutes), making it the system that admits clocks into the security model rather than pretending they are perfect (see ../../security/kerberos.md and ../../security/advanced/credential-rotation.md for key lifecycle).

The Shared Principle

Every auth mechanism is a lease on identity truth: the issuer grants a time-bounded, scoped claim; verifiers cache and check it locally; revocation is the problem of shortening a lease after the fact — and every revocation mechanism is a re-introduction of shared state, a key change, or a clock. This is the same lease structure as ./consensus-leases.md: session stores grant leases revocable by deletion (strong, centralized, a store to scale); JWTs grant leases revocable only by expiry, denylist, or key retirement (weak at the individual level, strong and instant at the key level — rotate the key and every outstanding token dies at once, the blunt-instrument property). The second shared idea: verification is always local; authority is always remote — SAML, OIDC, Kerberos, and JWT verification all follow “check a signature against keys obtained earlier,” which means the key cache is a replication of trust with its own staleness, exactly like a DNS resolver cache in ./dns-cdn-routing.md.

Where the Coupling HOLDS and BREAKS

JointCoupling that HOLDSWhere it BREAKS
JWT ↔ revocationSignature + exp gives bounded-lifetime verification with zero per-request stateAny real revocation requirement re-introduces state: jti denylists are a session store by another name; logout that only deletes the client copy is theater
OAuth2 ↔ OIDCClean layering: OAuth2 grants access to resources, OIDC adds the who (ID token)Teams conflate them: using OAuth2 access tokens as login proof (no audience/nonce binding) opens replay; access tokens are for APIs, ID tokens are for identity
Sessions ↔ scale-outShared store keeps revocation instant and state consistent across replicasThe store becomes the auth system’s single point of contention and failure — fail open (accept unverified) is unacceptable, fail closed (reject all) turns a store outage into a fleet-wide outage
Issuer ↔ verifier keyskid + cached JWKS/metadata gives rotation without redeploymentPropagation lag: verifiers that fetched keys an hour ago keep accepting tokens signed with a since-retired key; rotating and immediately removing the old key locks out everyone with cached keys — rotation needs an overlap window
exp/nbf ↔ clocksTime bounds give automatic, argument-free expiryEach verifier uses its own clock; NTP drift of even seconds causes intermittent 401 flapping on individual hosts; tolerance leeway trades flapping for a window of early/late validity (Kerberos formalized this decades ago)

Failure Propagation Across the Pipeline

The “stateless JWT” myth, traced to its three hidden stores. A team replaces session cookies with JWTs to “eliminate the Redis cluster.” Walk the joints. (1) Key distribution: every verifier now needs issuer public keys — a JWKS cache that must refresh, must handle kid misses, and must survive the issuer’s key endpoint being down (fail with stale keys or fail closed?). (2) Revocation: the day someone asks “log this user out now,” the answer is a denylist keyed by jti stored… in Redis, sized comparable to the session store, with TTL set to exp — the state moved, not vanished (this is the exact pattern the Why-Not Handbook’s stateless-vs-stateful analysis predicts). (3) Claim freshness: role changes, bans, and plan downgrades appear only at re-issue; the access token’s lifetime is now the staleness budget for authorization decisions, so “stateless” tokens quietly push teams to shorten exp, which increases refresh traffic — moving load back to the issuer. The honest framing: JWTs trade per-request state for bounded staleness, and that is a good trade for read-mostly authorization across service boundaries — and a bad one for “ban this account instantly.”

Clock skew and key rotation as propagation channels. exp validation compares the token against each verifier’s clock. A fleet with NTP discipline is fine; one VM with a drifting RTC produces the infamous pattern — “only server 7 rejects my tokens” — with 401s clustered around token boundaries. The mitigation is explicit: NTP everywhere, a documented leeway (30–60 s is typical; Kerberos uses minutes), and tests that move clocks. Key rotation propagates through the key cache: publish the new key in JWKS, wait out “longest plausible verifier cache TTL + exp” before retiring the old key, then retire. Compress that window and you get the fleet-wide 401 storm; stretch it indefinitely and you hold two live signing keys, doubling the value of a key compromise. Identity-provider outages propagate the same way, in reverse: verifiers fail open on stale keys or closed on strict checking — a policy decision with fleet-wide blast radius.

A Concrete Worked Example

Sizing a revocation denylist, with numbers. A platform issues 500,000 access tokens/hour with exp = 15 minutes; average session holds 1 access token live. A denylist must hold only revoked, unexpired jtis. At a 0.1% revocation rate (fraud flags, support logouts), live entries ≈ 500,000/4 per 15-min window × 0.1% ≈ 125 entries — trivial. But suppose the team also denylists on every logout event (30% of users explicitly log out) and access tokens last 8 hours: entries ≈ (500,000 × 8) hourly cohort × 30% ≈ 1.2 M live keys — you have rebuilt the session store, with worse indexing and no sliding window. The arithmetic yields the design rule: denylist size scales as revocation rate × token lifetime; if that product approaches your user count, you wanted sessions (or short exp) all along.

The store outage behind the session cluster. Session stores and revocation stores are strongly-consistent shared state, and their failovers are where auth incidents live: when the store’s primary fails over, writes that landed on the losing side vanish — a logout that silently un-logs-out, a rotation that didn’t stick. ../failure-casebook/github-october-2018.md documents exactly this class: a 43-second network partition during a database failover let both sides accept writes, and reconciliation was hours of manual work. Auth data has the same shape and a nastier failure mode — accept-then-vanish is benign for a like count and unsafe for “this session was revoked.” Hence the invariant: revocation and refresh-token storage need monotonic, durable handling (quorum writes or consensus), not eventually-consistent caches — and fail-closed policy decisions should be made before the store fails, not during.

When This Connection Is Useful

  • Problem-solving heuristics. Any auth design question reduces to locating state and staleness budgets: Who holds truth (issuer, session store, IdP)? How long may a copy of a claim live (access-token exp, session TTL, JWKS cache TTL, SAML metadata cache)? How fast can truth be shortened (delete, denylist, key retirement)? Draw that table for any proposed design and the tradeoffs fall out mechanically.
  • Interview answers. “JWT vs sessions” is a false binary; the senior answer names the blend — short-lived JWT access tokens for service-to-service verification, stateful refresh tokens with rotation for user sessions, instant revocation only where legally required — and prices each part in staleness. “How would you implement SSO across five internal tools?” is answered with the trust-chain structure: one IdP signs, RPs verify against published keys, cache TTLs define how quickly key or policy changes propagate, and clock discipline is an explicit operational requirement.
  • Engineering decisions. Keep access tokens short and scoped (aud-bound); rotate refresh tokens on use and store them durably; run NTP with monitoring and set leeway explicitly; rotate signing keys on a schedule with an overlap window sized by your verifiers’ cache TTLs; treat the JWKS/metadata endpoint as production-critical infrastructure with its own SLOs; and when a “stateless” migration is proposed, ask where revocation, key distribution, and claim freshness moved to — see ../assumptions-hunt/api-contracts.md for the contract-level assumptions tokens encode.

Interview Questions

  1. A team claims their JWT-based system is “fully stateless.” Trace the three kinds of state the design still contains, and give the workload where each becomes a scaling or correctness problem.
  2. Your security team demands logout that takes effect within 10 seconds everywhere. Compare four mechanisms — short exp, jti denylist, refresh-token revocation, signing-key rotation — on propagation speed, state cost, and blast radius.
  3. Users report intermittent 401s from one pod out of forty. How do exp/nbf validation and clock drift explain the pattern, and what are the two standard mitigations with their security costs?
  4. Walk through a signing-key rotation end to end: what must be true about the JWKS cache TTLs of every verifier before the old key can be retired, and what incident does compressing that window cause?
  5. During a session-store failover partition, both sides accepted a revocation write; the losing side’s write vanished. Why is this class of failure more dangerous for auth data than for ordinary application data, and what storage guarantees prevent it?

Key Takeaways

  • Auth is a pipeline — OAuth2 (delegation), JWTs (claims), sessions (state), cookies (transport), SSO (trust propagation) — and every mechanism is a lease on identity truth with a bounded staleness budget.
  • “Stateless JWT” relocates state rather than eliminating it: key distribution (JWKS caches), revocation (denylists are session stores by another name), and claim freshness (staleness budget = token lifetime).
  • Revocation mechanisms trade granularity against state: delete-key (instant, centralized), denylist (per-token, sized as revocation-rate × token-lifetime), key rotation (instant and total, but blunt), short exp (bounded staleness, no state).
  • Clock skew surfaces in exp/nbf as host-specific 401 flapping; Kerberos formalized skew tolerance decades ago — NTP discipline plus explicit leeway is the standard defense.
  • Key rotation propagates through verifier key caches: publish, wait out cache TTL + exp, then retire — compressing the window causes fleet-wide lockouts, stretching it doubles key-compromise value.
  • Revocation and refresh-token stores are strongly-consistent, security-critical state: failover-partition behavior must be designed (quorum/consensus, monotonic durability) before the incident, not after.

Cross-References