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 Content Security Policy Explained
A practical tour of CSP: what each directive does, how nonces and hashes work, how to roll out a policy safely with report-only mode, and the mistakes that quietly weaken it.
- Web Cookies vs localStorage vs sessionStorage
Compare the three main browser storage mechanisms by lifecycle, capacity, security, and use case so you pick the right tool for tokens, preferences, and state.
- Web CORS Explained In Depth
Understand Cross-Origin Resource Sharing: simple vs preflight requests, credentials, and the headers that decide whether your browser will let your fetch succeed.