X Xerobit

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...

Mian Ali Khalid · · 5 min read
Use the tool
JWT Decoder
Decode and inspect JSON Web Tokens. Local-only — tokens never leave your browser.
Open JWT Decoder →

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

FeatureSessionJWT
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 sizeSmall (session ID ~32 chars)Larger (payload + signature)
Database lookup per request✅ Yes❌ No
User data in token❌ No (just ID)✅ Yes (claims)
Cross-domainRequires CORS config✅ Authorization header works anywhere
Mobile/native appsHarder (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 posts

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.