JWT vs Session Authentication
Compare JSON Web Tokens with classic server-side sessions across storage, revocation, scaling, and security so you can choose the right model for your app.
What you'll learn
- ✓How sessions and JWTs actually work
- ✓Where to store each safely
- ✓How revocation differs
- ✓When stateless tokens earn their keep
- ✓Common security mistakes
Prerequisites
- •Familiar with HTTP
What and Why
Two patterns dominate web auth: server-side sessions and JSON Web Tokens. The internet has a tribal argument over which is “better,” but the right answer is almost always “it depends on your shape of system.”
A session is a server-held record. The client only carries an opaque ID. A JWT is a signed, self-describing token; the server can validate it without a lookup. That difference, statefulness, drives almost every other tradeoff.
Mental Model
Sessions: client holds a key, server holds the lock and the secret. Logging out throws away the lock.
JWTs: client holds a sealed envelope. Anyone with the public key can verify it. Server holds nothing. Logging out is harder because the envelope is still valid until it expires.
Sessions:
Client -> cookie: sid=abc -> Server
|
v lookup sid in store
|
user record
JWTs:
Client -> Authorization: Bearer eyJ... -> Server
|
v verify signature
|
claims (sub, exp, ...) Sessions trade memory and a database hop for instant revocation. JWTs trade revocation difficulty for stateless verification at any service.
Hands-on Example
Server-side session, cookie-based:
Set-Cookie: sid=AbC123...; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000
On each request the server reads the cookie, fetches sessions[sid] from Redis or a database, attaches the user. Logout deletes the row. Done.
A JWT in Authorization header:
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTcyMH0.sig
Decoded payload:
{
"sub": "42",
"exp": 1720000000,
"scope": "user:read user:write"
}
Any service with the public key can verify and trust the claims. A common pattern combines both: short-lived JWT access tokens (15 minutes) and a long-lived refresh token kept in an HttpOnly cookie. Access tokens stay stateless and quick; the refresh token is a session in disguise that can be revoked centrally.
Storage matters:
- Sessions:
HttpOnlycookie. Stops XSS-based theft. - JWT access tokens: in-memory in the JS app, or
HttpOnlycookie. NeverlocalStorageif you can avoid it. - Refresh tokens: always
HttpOnly,Secure,SameSite=Laxcookie.
Common Pitfalls
JWTs that never expire. A 1-year token is a 1-year vulnerability. Use short expirations and refresh.
Trying to revoke JWTs by maintaining a blacklist. Now you have state, but distributed. You have invented sessions with extra steps.
Storing JWTs in localStorage. Convenient, but any XSS exfiltrates the token instantly.
Hand-rolling JWT verification. Use a battle-tested library. Algorithm-confusion attacks (none, HS256 vs RS256 mixups) have hit major projects.
Forgetting CSRF with cookie-based auth. Cookies are sent automatically. Use SameSite=Lax and a CSRF token for non-GET requests.
Putting sensitive data in JWT claims. They are base64-encoded, not encrypted. Anyone who has the token can read it.
Skipping rotation. Refresh tokens should rotate on use. A stolen refresh token used twice should trip an alarm and revoke the family.
Practical Tips
- For monolithic apps with a single backend: sessions are simpler, faster to revoke, and easier to reason about.
- For microservices or multiple backends without a shared session store: JWTs (or signed access tokens) shine because verification is cheap and stateless.
- Combine both: stateless access tokens for fast verification, stateful refresh tokens for revocation control.
- Keep access tokens short (5 to 15 minutes). The shorter they are, the less revocation matters.
- Sign with asymmetric keys (RS256, EdDSA) so consumer services only need the public key. This avoids spreading shared secrets.
- Include a
jti(token ID) so you can selectively revoke if needed. - Track sessions per device in your store. Users want a “log out everywhere” button.
- Log auth decisions. When something is wrong, you will want to see exactly which token was accepted or rejected.
Wrap-up
Sessions are not legacy. JWTs are not magic. They solve different problems.
If you can afford a session lookup and have a single backend, sessions are usually the cleaner default. If your traffic crosses service boundaries or runs at edge scale, JWTs earn their complexity. Most mature systems land on a hybrid: short JWTs guarded by a server-controlled refresh flow.
Pick the model that matches your operational reality, store tokens correctly, and revisit the decision when your architecture changes.
Related articles
- Backend JWT Authentication: How It Works and Where It Fails
A clear-eyed guide to JWTs: structure, signing, verification, refresh flows, and the real-world failure modes nobody warns you about.
- Web SPA vs SSR vs SSG: Rendering Strategies Explained
Understand Single Page Apps, Server-Side Rendering, and Static Site Generation — how each works, performance tradeoffs, SEO impact, and when to use which.
- Web Web Authentication with OAuth 2.0 and OpenID Connect
Implement secure authentication using OAuth 2.0 and OIDC: authorization code flow with PKCE, ID tokens, scopes, token management, and common security pitfalls.
- Web Core Web Vitals Optimization Techniques
Hands-on techniques to improve LCP, INP, and CLS scores: image optimization, script deferral, layout stability patterns, and interaction responsiveness strategies.