Authentication
JWT
Session
Web Security
Revocation Service

Session vs JWT authentication with revocation service

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Session and JWT authentication both solve the same problem: prove user identity on each request. The real design question is how you want to manage state and revocation under load. If you need immediate logout and strict central control, sessions are usually simpler, while JWT can reduce server coupling when designed with short-lived access and strong refresh-token controls.

Session Authentication: Stateful Control

In a session model, the browser stores only an opaque session identifier, usually in an HTTP-only cookie. The server stores session data in Redis or another shared store, and every request checks that server-side record. Because the server owns state, revocation is straightforward: delete the session key and the user is effectively logged out.

This pattern works well for traditional web apps and internal tools where immediate invalidation matters more than fully stateless APIs. It also makes permission changes easy. If a user loses access to an admin feature, the next request can read updated roles from the session store and enforce the new policy without waiting for token expiration.

javascript
1const express = require('express');
2const session = require('express-session');
3const RedisStore = require('connect-redis').default;
4const { createClient } = require('redis');
5
6const app = express();
7app.use(express.json());
8
9const redisClient = createClient({ url: process.env.REDIS_URL });
10redisClient.connect();
11
12app.use(
13  session({
14    store: new RedisStore({ client: redisClient }),
15    secret: process.env.SESSION_SECRET,
16    resave: false,
17    saveUninitialized: false,
18    cookie: {
19      httpOnly: true,
20      secure: true,
21      sameSite: 'lax',
22      maxAge: 1000 * 60 * 60
23    }
24  })
25);
26
27app.post('/login', (req, res) => {
28  req.session.userId = 'u_123';
29  req.session.roles = ['member'];
30  res.json({ ok: true });
31});
32
33app.post('/logout', async (req, res) => {
34  req.session.destroy((err) => {
35    if (err) return res.status(500).json({ error: 'logout failed' });
36    res.clearCookie('connect.sid');
37    res.json({ ok: true });
38  });
39});

JWT Authentication: Stateless Access and Controlled Refresh

JWT shifts request authentication toward self-contained signed tokens. The server verifies the signature and expiration, often without querying session storage. This can simplify horizontal scaling for high-throughput APIs, but revocation becomes harder because a valid signed token remains valid until expiry.

A practical production pattern is:

  • Access token with a short lifetime, such as five to fifteen minutes.
  • Refresh token stored in a database with device metadata.
  • Rotation on every refresh call.
  • Revocation by deleting refresh rows and optionally blacklisting current access token identifiers for the remaining access lifetime.
javascript
1const jwt = require('jsonwebtoken');
2const crypto = require('crypto');
3
4function issueAccessToken(userId, tokenVersion) {
5  return jwt.sign(
6    {
7      sub: userId,
8      ver: tokenVersion,
9      jti: crypto.randomUUID()
10    },
11    process.env.ACCESS_TOKEN_SECRET,
12    { expiresIn: '10m' }
13  );
14}
15
16function verifyAccessToken(token) {
17  return jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
18}
19
20// Example middleware
21function requireAuth(req, res, next) {
22  const auth = req.headers.authorization || '';
23  const token = auth.startsWith('Bearer ') ? auth.slice(7) : null;
24  if (!token) return res.status(401).json({ error: 'missing token' });
25
26  try {
27    req.auth = verifyAccessToken(token);
28    next();
29  } catch (err) {
30    res.status(401).json({ error: 'invalid token' });
31  }
32}

Revocation Service Design

A revocation service closes the gap between stateless verification and real-world security requirements. You can combine three controls to get predictable behavior:

  1. Refresh-token registry. Keep active refresh tokens in persistent storage with expiry, device name, and last-seen timestamp.
  2. Token versioning. Store token_version on the user record. If you increment it after password reset or account compromise, all previously issued access tokens with old version become invalid.
  3. Access-token denylist for high-risk events. Store jti values in Redis with a TTL equal to remaining access-token lifetime.
sql
1create table refresh_tokens (
2  id uuid primary key,
3  user_id text not null,
4  token_hash text not null,
5  device_name text not null,
6  expires_at timestamp not null,
7  revoked_at timestamp,
8  created_at timestamp not null default now()
9);
10
11create index refresh_tokens_user_idx on refresh_tokens(user_id);
javascript
1// On security event, bump token version and revoke refresh tokens
2async function revokeAllSessions(db, userId) {
3  await db.query('update users set token_version = token_version + 1 where id = $1', [userId]);
4  await db.query('update refresh_tokens set revoked_at = now() where user_id = $1 and revoked_at is null', [userId]);
5}

This hybrid approach gives you most of the scaling benefits of JWT while keeping emergency lockout and logout behavior deterministic.

Common Pitfalls

  • Long-lived access tokens. Fix by using short expiration and rotating refresh tokens.
  • Storing tokens in insecure browser storage. Fix by preferring HTTP-only cookies when possible and always using TLS.
  • No device-level revocation. Fix by storing refresh token records per device so users can revoke one session without logging out everywhere.
  • Missing clock-skew handling. Fix by allowing small leeway during verification and keeping system time synchronized.
  • Treating JWT as fully stateless while needing immediate revocation. Fix by adding token version checks and a targeted denylist.

Summary

  • Session auth is stateful and easy to revoke immediately.
  • JWT can scale well, but revocation requires explicit architecture.
  • Short-lived access plus rotated refresh tokens is the most reliable JWT baseline.
  • A revocation service should include refresh registry, token versioning, and selective access-token denylisting.
  • Choose based on security requirements first, then optimize for operational simplicity.

Course illustration
Course illustration

All Rights Reserved.