JWT vs Session Authentication — When to Use Each
JWT and session authentication have different tradeoffs. Sessions store state server-side; JWTs are stateless. Learn when stateless JWTs are genuinely useful, when sessions are...
Use the tool
JWT Decoder
Decode and inspect JSON Web Tokens. Local-only — tokens never leave your browser.
JWTs aren’t “better” than sessions — they have different tradeoffs. Sessions are simpler and more revocable; JWTs are better for distributed systems and third-party access.
Inspect JWT tokens with the JWT Decoder.
How each approach works
Session authentication:
1. User logs in → server creates session in database
2. Server sets sessionId cookie (httpOnly)
3. Each request: browser sends cookie → server queries database for session
4. Logout: delete session from database → immediately revoked
JWT authentication:
1. User logs in → server creates JWT (signed, not stored)
2. Client stores JWT (localStorage or cookie)
3. Each request: client sends JWT → server verifies signature
4. Logout: client deletes JWT → but server can't revoke it until it expires
Comparison
| Feature | Session | JWT |
|---|---|---|
| Server state | ✅ Stored in DB | ❌ Stateless (not stored) |
| Immediate revocation | ✅ Delete session row | ❌ Must expire or use blocklist |
| Horizontal scaling | ❌ Need shared session store | ✅ Any server can verify |
| Token size | Small (session ID ~32 chars) | Larger (payload + signature) |
| Database lookup per request | ✅ Yes | ❌ No |
| User data in token | ❌ No (just ID) | ✅ Yes (claims) |
| Cross-domain | Requires CORS config | ✅ Authorization header works anywhere |
| Mobile/native apps | Harder (cookies) | ✅ Easy (Authorization header) |
When JWT is genuinely better
// 1. Microservices — service B trusts user claims from service A:
// Service A issues JWT; Service B verifies signature without calling A
// No shared session database needed
// 2. Third-party API access — OAuth 2.0 access tokens:
// Your API accepts tokens from Google, GitHub, Auth0
// Verify their JWTs without calling their servers on every request
// 3. Serverless / edge functions:
// Lambdas and edge workers can't share session memory
// JWT verification is stateless — works anywhere
// 4. Mobile/native apps:
// No cookie jar → Authorization header with JWT is natural
const res = await fetch('/api/data', {
headers: { Authorization: `Bearer ${accessToken}` }
});
When sessions are better
// 1. Single monolith with a database:
// Sessions are simpler — no token storage, no expiry management
// Revocation is instant (delete the row)
// 2. You need immediate revocation:
// User changes password? Revoke all sessions instantly.
// Suspicious activity detected? Kill the session.
// With JWT you either wait for expiry or maintain a blocklist.
// 3. Sensitive applications (banking, healthcare):
// Session gives you fine-grained control
// JWT's statelessness is a liability when you need immediate logout
// 4. Small teams:
// Session auth is simpler to reason about and debug
// Fewer attack vectors (no token storage decisions)
The hybrid approach (best of both)
// Use short-lived JWTs (15 minutes) + server-stored refresh tokens:
//
// Access token: JWT, 15min, stateless
// Refresh token: opaque ID, stored in DB, revocable
//
// Benefits:
// - No DB lookup for most requests (JWT verify is fast)
// - Revocation via refresh token (logout = delete refresh token)
// - Works for distributed systems
// - Immediate effective revocation (15min max window)
// This is how Auth0, Firebase, and most auth providers work
Session with Redis (scalable)
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
},
}));
// Login:
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
req.session.userId = user.id;
req.session.roles = user.roles;
res.json({ message: 'Logged in' });
});
// Logout (immediate revocation):
app.post('/logout', (req, res) => {
req.session.destroy(() => res.clearCookie('connect.sid').json({ message: 'Logged out' }));
});
Making the choice
Q: Do you have multiple services that need to trust authentication?
Yes → JWT (or OAuth 2.0)
Q: Do you need immediate revocation (password changes, suspicious activity)?
Yes → Sessions, or JWT + refresh token + blocklist
Q: Are you building for serverless / edge compute?
Yes → JWT
Q: Is this a simple monolith with one database?
Yes → Sessions (simpler, less error-prone)
Q: Are mobile apps your primary client?
Yes → JWT in Authorization header
Q: Do you have strict compliance requirements?
Yes → Sessions (clearer audit trail, immediate revocation)
Related tools
- JWT Decoder — inspect JWT token structure
- Password Generator — generate session secrets
- Hash Generator — generate session IDs
Related posts
- Decoding a JWT Is Not the Same as Verifying It — Every JWT bug in production reduces to the same mistake: trusting a decoded toke…
- JWT Security Checklist for 2026 — Twelve checks every JWT implementation should pass before shipping. The actual c…
- JWT Authentication Flow — Login, Token Storage, Refresh Tokens — JWT authentication involves issuing tokens on login, sending them with requests,…
- JWT Standard Claims — iss, sub, aud, exp, nbf, iat, jti Explained — JWT registered claims define standard metadata for tokens: issuer (iss), subject…
- JWT Security Best Practices — Common Vulnerabilities and How to Avoid Them — JWT vulnerabilities include algorithm confusion attacks, weak secrets, missing e…
Related tool
JWT Decoder
Decode and inspect JSON Web Tokens. Local-only — tokens never leave your browser.
Written by Mian Ali Khalid. Part of the Encoding & Crypto pillar.