Auth and sessions
Session stores, JWT trade-offs, refresh tokens, revocation, and authorization boundaries.
Session vs JWT is not a religion; it's a trade-off between revocation and statelessness. Most systems need both: short JWT access tokens for speed, long refresh tokens with server state for revocation.
Read this if your last attempt…
- You said "we'll use JWT" without knowing what happens when a token is compromised
- You don't know the OAuth authorization-code flow
- You can't explain refresh tokens
- You confuse authentication with authorization
The concept
Authentication (authN) is "who are you?". Authorization (authZ) is "what can you do?". Tokens carry identity (authN); policy decisions (authZ) usually happen at the server with the token's identity as input.
Session cookies:
- Server generates a random session id, stores (session_id → user + metadata) in a session store (Redis, DB).
- Client sends it back as a cookie; server looks it up each request.
- Revocation: delete the row. Instant.
- Cost: a lookup per request. Cheap with Redis, non-trivial at very high scale.
Short JWT for speed; long refresh token stored server-side for revocation.
Session vs JWT — trade-off at a glance.
| Session cookie | Plain JWT | Access + refresh | |
|---|---|---|---|
| Revocation | Instant (delete row) | Not without denylist | Fast (delete refresh row) |
| Per-request cost | One store lookup | Signature verify only | Sig verify (access) + occasional refresh |
| Stateless? | No (server state) | Yes | Mostly (refresh is stateful) |
| Multi-service friendly | Shared session store | Verifiable anywhere | Access verifiable anywhere |
| Best for | Server-rendered web apps | Service-to-service, short-lived | Most web + mobile apps |
How interviewers grade this
- You distinguish authN from authZ.
- You pick access+refresh over plain JWT, with revocation story.
- You name the OAuth flow (authorization-code with PKCE for SPAs).
- You address cookie security: HttpOnly, Secure, SameSite.
- You rotate on sensitive actions (password change → invalidate all sessions).
Variants
Session cookie + server store
Classic: random id in a cookie, state in Redis.
Simple, revocable, well-understood. Requires a session store on the hot path — fine with Redis at millions of sessions, a bottleneck at very high multi-region scale.
Pros
- +Trivial revocation
- +No token-tampering risk
- +Simple mental model
Cons
- −Hot-path lookup per request
- −Multi-region session store is non-trivial
- −Cookies bring CSRF concerns — handle with SameSite + CSRF tokens
Choose this variant when
- Server-rendered web apps
- Single-region products
- Revocation is primary concern
Access + refresh token
Short JWT access token + server-stored refresh token.
The modern default for APIs + SPAs + mobile. Access JWT (5–15 min) is verified locally — fast. When it expires, client exchanges refresh token at the auth service — rotate, return new pair. Revoke = delete refresh-token row.
Pros
- +Fast per-request (no lookup on access JWT)
- +Revocable (delete refresh)
- +Works across services with just the signing key
Cons
- −Complexity in client (handle rotation)
- −Brief window between compromise and expiry where access JWT is live
Choose this variant when
- APIs, SPAs, mobile apps
- Multi-service architectures
- Most modern products
OAuth 2.0 social login (code flow + PKCE)
Delegate authentication to Google/Apple/etc.
Your app gets tokens from the IdP after the user consents. You then mint your own internal session + refresh tokens so future requests don't need the IdP. IdP tokens are for calling the IdP's APIs; your tokens are for your app.
Pros
- +No password storage
- +User trust — they know Google
- +Single-tap login on mobile
Cons
- −Dependency on IdP availability
- −Email-ownership edge cases (merge accounts?)
- −Token refresh with IdP is an ongoing concern
Choose this variant when
- Consumer apps with social login
- Enterprise SSO (OIDC)
- Anywhere you'd rather not run password UX
Worked example
Design: auth for a SaaS app with web + mobile clients.
Login flow:
- Email + password → Argon2id hash compare. Rate-limit per identity (5 attempts / 15 min). Optional 2FA.
- On success: mint access JWT (15 min, user id + org id + roles claim) + refresh token (30 days, random 256-bit, stored in DB).
- Cookies (web): HttpOnly + Secure + SameSite=Lax. Mobile: Authorization: Bearer header.
Request flow:
- Every request verifies the access JWT signature locally (no lookup).
- On 401, client exchanges refresh token for a new access + rotated refresh. Server deletes the old refresh, creates a new one — prevents token replay if stolen.
Revocation:
- User logs out → delete their refresh-token row. Access JWT still live for up to 15 min — acceptable for most products.
- Password change → delete ALL refresh tokens for the user → next access-token refresh fails → forced re-login everywhere.
- Compromised server-side: rotate signing key → all access JWTs invalidate instantly.
Multi-region:
- Signing key distributed via KMS. Any region verifies JWTs.
- Refresh-token DB is per-region with cross-region replication (for failover). Accept seconds of replication lag on refresh across regions.
Good vs bad answer
Interviewer probe
“Sessions or JWT?”
Weak answer
"JWT because it's stateless."
Strong answer
"Access + refresh. Short JWT access token (15 min) verified locally for per-request speed. Long refresh token (30 days) stored server-side; rotated on each use. Revocation = delete the refresh row; the access JWT is live for up to 15 min which is our accepted blast radius. Plain JWT without a refresh-tier gives up revocation entirely — fine for pure M2M but not for human users. Plain session cookies are also fine, just pay the lookup per request — acceptable until you're multi-region where session-store consistency gets painful."
Why it wins: Names the hybrid, quantifies the revocation window, contrasts the alternatives, and notes when each pure model wins.
When it comes up
- Login, signup, "how do users authenticate?"
- Multi-service or multi-region request flows that carry identity
- Web + mobile clients sharing one backend
- Third-party / social login or enterprise SSO
Order of reveal
- 11. Separate authN from authZ. Authentication answers who you are; authorization answers what you can do. The token carries identity; policy decisions happen at the service.
- 22. Access + refresh default. Short-lived access JWT (5–15 min) verified locally for speed, plus a long-lived refresh token stored server-side.
- 33. Revocation story. Revoke by deleting the refresh-token row; the access token’s blast radius is just its short lifetime. Rotating the signing key invalidates everything at once.
- 44. Token storage + cookie flags. HttpOnly + Secure + SameSite on cookies; never localStorage for tokens because any XSS can read it.
- 55. Federated login. For social/SSO, OAuth 2.0 authorization-code flow, plus PKCE for mobile and SPAs that cannot hold a client secret.
Signature phrases
- “AuthN is who you are; authZ is what you can do.” — The distinction sloppy candidates blur and interviewers probe.
- “Short access JWT for speed, server-stored refresh for revocation.” — Names the hybrid that gets both performance and a kill switch.
- “Revoke the refresh row; the access token's blast radius is its 15-minute life.” — Shows you reason about the compromise window quantitatively.
- “HttpOnly, Secure, SameSite — non-negotiable on auth cookies.” — Demonstrates baseline web-security hygiene.
Likely follow-ups
?“How do you revoke a JWT immediately?”Reveal
You cannot revoke a bare JWT before it expires — that is the trade-off for stateless verification. Options: (1) keep access tokens short (5–15 min) and revoke the refresh token so the chain dies at next refresh; (2) maintain a small denylist of revoked token ids checked on each request (this reintroduces a lookup); (3) rotate the signing key to invalidate every token at once for a mass-compromise event.
?“Service B receives a request with a token minted by the auth service. How does it trust it?”Reveal
B verifies the JWT signature locally using the auth service’s public key, distributed via a JWKS endpoint (or KMS). No central lookup on the hot path — that is the whole point of a signed token. B reads the identity + roles claims and applies its own authorization. Key rotation is handled by publishing the new key to JWKS with an overlap window.
?“Where should the browser store the tokens?”Reveal
In an HttpOnly, Secure, SameSite cookie — not localStorage or sessionStorage. localStorage is readable by any JavaScript on the page, so a single XSS exfiltrates the token. HttpOnly keeps it out of JS entirely; SameSite blunts CSRF; Secure keeps it off plaintext HTTP. Pair with a CSRF token or SameSite=Strict/Lax for state-changing requests.
Common mistakes
If a refresh token is stolen and not rotated on use, attacker keeps refreshing forever. Rotate on every exchange; invalidate the old. If an old one is used again, that's a theft signal — invalidate the whole chain.
Compromised JWT lives forever. Keep access JWT expiry short (5–30 min); pair with refresh for UX.
HttpOnly prevents JS theft. Secure prevents HTTP downgrade. SameSite=Lax prevents CSRF on most cross-site contexts. All three are non-optional on session/refresh cookies.
Vulnerable to XSS. Any script can read it. Put auth tokens in HttpOnly cookies, not in JS-accessible storage.
Practice drills
User reports their account was accessed from another city. What do you do?Reveal
Revoke every refresh token for that user. Access JWTs expire within 15 min naturally. Force re-authentication everywhere; step up with 2FA on next login. Review logs for the session's actions. Emit a "new device" email so the user can confirm / escalate.
Interviewer: "why not JWT everywhere, no server state?"Reveal
Because revocation is the feature you'll wish you had. A stolen JWT is live until expiry. For short-lived M2M tokens (5 min) that's acceptable. For 24-hour user tokens it's a liability. The hybrid (short JWT + server refresh) gets you most of the performance with a fast revocation knob.
Explain PKCE in one paragraph.Reveal
Proof Key for Code Exchange — for OAuth clients that can't safely hold a client_secret (mobile apps, SPAs). Client generates a code_verifier (random string), hashes it → code_challenge, sends the challenge with the authorization request. After getting the authorization code, client sends it back along with the original verifier. Server checks that sha256(verifier) == challenge. This proves the code-redeemer is the same client that initiated the flow, without a pre-shared secret.
Cheat sheet
- •AuthN = who. AuthZ = what can they do. Keep distinct.
- •Modern default: short access JWT + long server-stored refresh.
- •Rotate refresh on every use; detect replay.
- •Cookies: HttpOnly + Secure + SameSite=Lax, always.
- •OAuth 2.0 code flow for server; + PKCE for mobile/SPA.
- •Sensitive action → invalidate all sessions.
- •Rotate signing keys periodically.
Practice this skill
No problem is tagged directly to Auth and sessions yet. These published problems still exercise the same interview category.
Read this if