Session & Auth Tokens
Every chapter so far has been building toward this one — cookies (Ch1-3), security attributes (Ch2), cross-origin behaviour (Ch5), and headers (Ch6) all matter most in exactly this context: keeping a user logged in safely. This chapter compares the two dominant approaches to web authentication, and answers the question that trips up almost every developer at some point: where should a token actually live?
Server-Side Sessions — The Traditional Approach
The server creates a session record (in memory, a database, or Redis) and gives the browser only a short, random session ID — the actual user data and permissions stay entirely on the server, looked up by that ID on every request.
Set-Cookie: session_id=a1b2c3d4e5f6; Secure; HttpOnly; SameSite=Lax
// What the server holds, looked up by that ID:
{
"user_id": 42,
"role": "admin",
"expires": "2026-07-01T12:00:00Z"
}
JWT (JSON Web Tokens) — The Stateless Approach
Instead of an opaque ID looked up server-side, a JWT contains the actual claims (user ID, role, expiry) directly, encoded and cryptographically signed — the server can verify it's genuine and unmodified without needing to look anything up in a database at all.
Three parts, separated by dots: a header (algorithm info), a payload (the actual claims), and a signature (proving it hasn't been tampered with).
Comparing the Two Approaches
Scaling: needs a shared session store (Redis, a database) if running multiple servers, so any server can look up any session.
Best for: traditional web apps with a single backend, anything needing instant logout/revocation (banking, admin panels).
Scaling: any server can verify the signature independently — no shared session store needed.
Best for: APIs consumed by multiple independent services, mobile app backends, microservice architectures.
Where to Store a Token — The Question That Actually Matters
Regardless of which approach is used, the token has to live somewhere in the browser — and this decision has real security consequences, directly building on Chapter 2's Secure/HttpOnly coverage.
| Storage location | Vulnerable to XSS? | Vulnerable to CSRF? | Verdict |
|---|---|---|---|
| HttpOnly cookie | No — invisible to JS | Yes, without SameSite/CSRF tokens | Best overall, pair with SameSite=Lax/Strict |
| localStorage | Yes — fully readable by any script | No — never sent automatically | Common but risky if XSS is even slightly possible |
| sessionStorage | Yes — same exposure as localStorage | No | Same risk as localStorage, just shorter-lived |
| In-memory JS variable only | Reduced — gone if the script context is destroyed | No | Most XSS-resistant, but lost on every page refresh |
A Sensible Default for Most Projects
- Store the actual session/auth token in an HttpOnly, Secure cookie with SameSite=Lax. This is the combination covered across Chapters 2 and this one that resists the most realistic attack scenarios.
- Use a strict CSP (Chapter 6) as the primary defence against XSS, rather than relying on storage location alone.
- If building a separate API consumed by a mobile app or third-party service (where cookies don't naturally apply), JWTs with short expiry plus refresh tokens are the standard, well-understood pattern.
Chapter 7 Quick Reference
- Server-side sessions — opaque ID in a cookie, real data server-side; instant revocation, needs a shared store at scale
- JWT — self-contained, signed claims; no database lookup needed, but genuinely hard to revoke before expiry
- JWT payload is readable by anyone — signed against tampering, not encrypted against reading
- JWT revocation pattern: short-lived JWT + longer-lived refresh token, revoke the refresh token
- HttpOnly cookie — safe from XSS, needs SameSite against CSRF; localStorage — safe from CSRF, fully exposed to XSS
- No storage location is immune to both — prevent XSS via CSP, prevent CSRF via SameSite, rather than relying on storage choice alone
- Sensible default: HttpOnly + Secure + SameSite=Lax cookie for session tokens; JWT + refresh tokens for separate/mobile APIs
- Next chapter (capstone): debugging real browser console errors — a practical walkthrough of common storage/CORS/cookie messages