Sessions vs Tokens
Two real ways to keep a user logged in across requests — cookie-backed sessions and self-contained JWTs — and their actual tradeoffs.
3 min read
HTTP is stateless — the server doesn't inherently remember one request to the next — so after a user logs in, the backend needs some mechanism to recognize them on every following request without asking for a password again. The two dominant approaches are session-based auth and token-based auth, and they solve the problem in genuinely different ways.
Session-based authentication
The server creates a session record (in memory, or more commonly in a shared store like Redis) after login, and sends the client an opaque session ID in a cookie. The cookie itself carries no meaningful data — it's just a lookup key.
POST /login → Set-Cookie: session_id=abc123; HttpOnly; Secure
GET /profile
Cookie: session_id=abc123
→ Server looks up "abc123" in its session store → finds user 42 → responds
- Revocation is trivial — delete the session record server-side, and the cookie instantly stops working.
- Requires shared state — every server instance handling requests needs access to the same session store, which is extra infrastructure.
- Server does a lookup on every request — a small amount of overhead (usually a fast Redis read) per request.
Token-based authentication (JWTs)
The server issues a JWT (JSON Web Token) — a signed, self-contained token that encodes the user's identity and claims directly, with no server-side record needed to validate it.
POST /login → { "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.signature..." }
GET /profile
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
→ Server verifies the signature, trusts the embedded claims, no lookup needed
A JWT has three parts — header, payload (claims like sub, exp), and a signature that proves the server issued it and it hasn't been tampered with.
- Stateless and scales easily — any server that knows the signing secret can verify a token with no shared session store and no database round trip.
- Revocation is hard — since there's no server-side record, a JWT stays valid until it expires, even if you'd want to invalidate it right now (a compromised account, a logout). Fixing this requires extra infrastructure anyway — a token blocklist, or short expiries.
- Bigger payload — the token itself is sent on every request and is larger than an opaque session ID.
The tradeoff, stated plainly
Sessions trade a small per-request lookup cost for instant, simple revocation. JWTs trade instant revocation for statelessness and easy horizontal scaling. Neither is strictly better — it depends on which cost you'd rather pay.
The common middle ground
Most production systems today don't pick one purely — they use short-lived JWT access tokens (minutes to an hour) for fast, stateless verification on most requests, plus a longer-lived, server-tracked refresh token to issue new access tokens. This gets most of the statelessness benefit of JWTs during normal use, while capping how long a compromised access token stays dangerous, and keeps meaningful revocation available through the refresh token.
Whichever mechanism carries identity, the server still needs a consistent place to check it on every request — which is exactly the job of middleware, covered next.