Authentication comparison
JWT vs Session Tokens
Two fundamentally different approaches to keeping users logged in. The choice shapes how your backend scales, how quickly you can revoke access, and where you store secrets — and it comes up constantly in architecture reviews and security discussions.
TL;DR
- Session tokens are random IDs stored server-side. Every request hits a session store to look up the user. Revocation is instant. Scales vertically or with a shared cache (Redis).
- JWTs are self-contained signed tokens. The server validates the signature locally — no database call. Revocation before expiry is hard without extra infrastructure.
- The real trade-off is stateless convenience vs. instant revocation control. Many production systems use both: a short-lived JWT as an access token and a session-like refresh token stored server-side.
Side-by-side comparison
| Aspect | Session Tokens | JWT |
|---|---|---|
| State | Stateful — session data lives on the server | Stateless — all data encoded in the token |
| Scalability | Requires sticky sessions or a shared session store (Redis) | Any server validates independently — scales horizontally |
| Revocation | Instant — delete the session record | Hard — token is valid until expiry (need a blocklist to revoke early) |
| Storage (client) | HttpOnly cookie (just the ID) | HttpOnly cookie or localStorage (full token) |
| Size | Small — 20–40 byte random ID | Larger — typically 200–500 bytes of Base64 data |
| Server-side DB call | Every request | None (signature check only) |
| Token rotation | Easy — regenerate on login/privilege change | Needs refresh-token rotation pattern |
| Best for | Web apps where instant logout matters | Stateless APIs, microservices, mobile clients |
When to use session-based auth
- You need instant revocation. Compromised account? Delete the session record and the user is logged out immediately — no waiting for a token to expire.
- Traditional web application. Server-rendered apps with a single backend and a cookie-based flow are a natural fit for sessions.
- Sensitive operations (banking, healthcare). The ability to force-logout users across all devices by clearing their sessions is a compliance requirement in many regulated industries.
- Small scale. If you have one server or a Redis instance, the session store is not a bottleneck.
When to use JWT
- Stateless microservices. Each service validates the token locally without calling an auth service, reducing coupling and latency.
- Mobile and SPA clients. A JWT in memory or secure storage travels with each API request without relying on cookie handling.
- Cross-domain auth. JWTs work across domains naturally; cookies require careful SameSite and CORS configuration.
- Short-lived access tokens with refresh tokens. A 5-minute JWT minimises the revocation window while keeping the system stateless for most requests.
English phrases engineers use
JWT conversations
- "We use JWT for stateless auth across our microservices."
- "The token is signed with HS256 using a shared secret."
- "You need to validate the signature before trusting any claim."
- "The access token expires in 15 minutes; the refresh token lasts 7 days."
- "We store a JTI in Redis to maintain a blocklist for revoked tokens."
Session conversations
- "The session store is a bottleneck under high load — we're moving to Redis."
- "We rotate the session ID on login to prevent session fixation."
- "When the user logs out, we invalidate the session immediately."
- "We're using sticky sessions on the load balancer for now."
- "The session expires after 30 minutes of inactivity."
Key vocabulary
- Claim — a key-value pair in the JWT payload (e.g.,
"sub": "user_42","exp": 1700000000). - Signature — a cryptographic hash of the header and payload, verifying the token was not modified.
- HS256 / RS256 — HMAC-SHA256 (symmetric, shared secret) vs. RSA-SHA256 (asymmetric, public/private key pair).
- Refresh token — a long-lived token used only to obtain a new short-lived access token.
- Session fixation — an attack where an adversary sets a known session ID before login; mitigated by rotating the ID on authentication.
- JTI (JWT ID) — a unique identifier claim used to track or blocklist individual tokens.
- HttpOnly cookie — a cookie inaccessible to JavaScript, reducing XSS exposure.
Quick decision tree
- Need instant logout / revocation across all devices → Sessions
- Stateless microservices that must validate tokens independently → JWT
- Traditional server-rendered web app → Sessions
- Mobile app or cross-domain SPA → JWT
- Regulated industry with forced-logout requirements → Sessions
- High horizontal scale, want to avoid shared session store → JWT
- Both scale and revocation matter → JWT access token + server-side refresh token
Frequently asked questions
Can you revoke a JWT before it expires?
Not easily — that is the main trade-off of stateless tokens. Because the server keeps no state, a valid JWT is accepted until it expires. Common workarounds include short expiry times (5–15 minutes) combined with refresh tokens, or maintaining a server-side blocklist (which reintroduces state). Some teams use Redis to store revoked JTI (JWT ID) values.
Is JWT more secure than session tokens?
Neither is inherently more secure — they have different threat surfaces. JWTs stored in localStorage are vulnerable to XSS; JWTs in HttpOnly cookies have similar protections to session cookies. Session tokens can be revoked instantly but are vulnerable to session fixation if not rotated on login. Security depends far more on implementation than on the choice of mechanism.
What is a refresh token?
A refresh token is a long-lived credential (days to weeks) stored securely, used only to obtain a new short-lived access token (JWT). The pattern lets you keep access tokens short-lived (reducing the revocation window) while not forcing users to log in frequently. Refresh tokens should be rotated on each use and stored in HttpOnly cookies.
What is the session store bottleneck?
In session-based auth, every incoming request must look up the session ID in a central store (database or cache like Redis). Under high traffic this store becomes a shared dependency that must scale with your application. With JWTs the token is self-contained — the server validates the signature locally without any database call.
What does a JWT actually look like?
A JWT is three Base64URL-encoded strings joined by dots: header.payload.signature. The header names the algorithm (e.g., HS256 or RS256). The payload contains claims — registered ones like "sub", "exp", "iat", plus any custom claims you add. The signature lets the server verify the token was not tampered with.
Should I store a JWT in localStorage or a cookie?
Prefer HttpOnly cookies. localStorage is accessible to any JavaScript on the page, so an XSS vulnerability can steal the token. An HttpOnly cookie cannot be read by JavaScript. Add the Secure and SameSite=Strict (or Lax) attributes, and pair with CSRF protection if you use SameSite=Lax.