SystemDesign.website
Track 8 ยท Module 8.1Security & Authentication

Token-Based Authentication & JWTs

How stateless JSON Web Tokens eliminate database session bottlenecks, the cryptographic mechanics of digital signatures (HMAC vs RSA), refresh token rotation, instant revocation via Redis blacklists, and defense against classic exploit vectors.

โฑ๏ธ Estimated Reading: 28 minsโ€ขPrerequisites: Module 1.1โ€ขInteractive Lab: JWT Inspector & Signature Lab
๐ŸŽŸ๏ธ REAL-WORLD ANALOGY ยท FIRST PRINCIPLES

1. Stateful Sessions vs. Stateless Tokens

To understand why modern distributed systems favor tokens, let's compare the two primary paradigms of web authentication using a nightclub analogy.

๐Ÿงฅ The Stateful Session Model (Coat Check)

When you check in, the club takes your ID, writes your details into a central ledger book (Database/Redis), and gives you a blank plastic token stamped #492 (Session ID Cookie).

The Bottleneck: Every time you order a drink or enter VIP, the bartender must run to the back room ledger to look up #492. When your nightclub scales to 500 bartenders across 10 floors, all 500 bartenders bottleneck waiting for access to that single ledger!

๐Ÿชช The Stateless Token Model (Holographic VIP Pass)

When you check in, the club gives you a wristband printed with your name, tier, and 2:00 AM expiration, stamped with a tamper-proof cryptographic hologram signature made with the manager's private seal.

Zero Database Lookups: Any bartender at any bar can mathematically verify the holographic seal under UV light in 2 microseconds. No trips to the back room database required!

AttributeServer-Side Sessions (Redis / SQL)Stateless JWTs (Bearer Tokens)
Storage LocationServer memory / Redis clusterClient (Memory or HttpOnly cookie)
Auth Verification Cost1 Network Round-Trip to Redis per request (~1-3ms)Pure CPU mathematical verification (~0.05ms)
Instant RevocationTrivial (Delete key from Redis immediately)Complex (Stateless tokens cannot be un-issued)
Cross-Domain / MicroservicesRequires shared Redis session storeNative (Any microservice with public key verifies)
Payload SizeTiny (32-byte UUID session ID)Larger (500B – 2KB Base64 JSON)
๐Ÿ” RFC 7519 SPECIFICATION

2. The Anatomy of a JSON Web Token

A JWT is a compact, URL-safe string composed of three distinct segments separated by periods (.):

A raw JWT string:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzg4MDgwMDAwfQ.4z9jK5kLmPQrStUvWxYz_aBcDeFgHiJkLmNoPqRsTuV
1. HEADER (Red)
Algorithm (HS256, RS256) & Token Type (JWT).
2. PAYLOAD (Purple)
Claims: Subject (User ID), Expiry (exp), Roles.
3. SIGNATURE (Blue)
Cryptographic hash ensuring data is untampered.
๐Ÿ“ How the Cryptographic Signature Is Generated:
Signature = HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    server_secret_key
)
      

If an attacker alters even a single character of the payload (e.g. changing "role": "user" to "role": "admin"), the signature calculated by the server will completely mismatch, causing the request to be instantly rejected!

๐Ÿ” CRYPTOGRAPHIC ARCHITECTURE

3. Symmetric (HMAC) vs. Asymmetric (RSA/ECDSA) Signing

In system design, choosing between Symmetric and Asymmetric token signing determines how your microservices share trust boundaries.

Symmetric: HS256 (Shared Secret)

Both the Auth Service (Signer) and all Resource Services (Verifiers) must possess the exact same secret key (my-secret-key-123).

โš ๏ธ Danger: If a single microservice with the secret key is compromised, the attacker can forge valid admin tokens for the entire ecosystem!

Asymmetric: RS256 / ES256 (Public/Private Keypair)

The Auth Service holds the Private Key (kept strictly secret) to sign tokens. All other microservices only receive the Public Key (via JWKS endpoint /.well-known/jwks.json).

โœ… Gold Standard: Even if 20 microservices are breached, none of them can forge tokens because none have the Private Key!
๐Ÿ”„ RESILIENCE ARCHITECTURE

4. The Dual-Token Architecture (Access + Refresh Tokens)

If a stateless JWT has a 30-day lifespan and an attacker intercepts it over Wi-Fi, the attacker has free access for 30 days. To solve this, production systems implement the Dual-Token Rotation Protocol:

Dual-Token Rotation Lifecycle Sequence
Client / AppAPI GatewayAuth Server1. POST /login (username + password)2. Return Access Token (15m JWT) + Refresh Token (7d in HttpOnly Cookie)3. GET /orders (Bearer AccessToken)[Gateway verifies JWT locally in 0.1ms → 200 OK]4. AccessToken Expired (401) → POST /refresh (Cookie: RefreshToken)5. Invalidate Old RefreshToken → Issue Brand New Access + Refresh Token
๐Ÿงช LIVE CRYPTO LAB

5. Interactive JWT Inspector & Signature Lab

Edit claims or tampering payload values. Observe how signature hashing works and see cryptographic verification fail in real-time when claims are altered without the secret key.

{ "alg": "HS256", "typ": "JWT" }
sub:
role:
exp (min):
GENERATED COMPACT JWT:VALID SIGNATURE
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfODQ5MjAiLCJyb2xlIjoidXNlciIsImV4cCI6MTc4ODA4MDAwMH0.dGhpcy1pcy1hLXNpbXVsYXRlZC1zaWduYXR1cmU
VERIFICATION VERDICT:
โœ“ Signature matches cryptographic hash.
โœ“ Token is active (expires in 15 minutes).
โœ“ Authorized Role: user
๐Ÿ’ป PRODUCTION PATTERNS ยท CODE EXAMPLES

6. Production JWT Verification in 5 Languages

Below is production-ready code demonstrating how to decode, verify cryptographic signatures, and enforce expiration thresholds.

jwt_auth_verifier
import jwt
from datetime import datetime, timezone, timedelta
from typing import Optional, Dict, Any

SECRET_KEY = "super-secure-production-secret-key-32b"
ALGORITHM = "HS256"

def create_access_token(user_id: str, role: str, expires_delta: timedelta = timedelta(minutes=15)) -> str:
    expire = datetime.now(timezone.utc) + expires_delta
    payload = {
        "sub": user_id,
        "role": role,
        "exp": expire,
        "iat": datetime.now(timezone.utc)
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_access_token(token: str) -> Optional[Dict[str, Any]]:
    try:
        # Crucial security: explicitly enforce algorithms whitelist to prevent alg:none exploit
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.ExpiredSignatureError:
        print("[AUTH] Token has expired")
        return None
    except jwt.InvalidTokenError as e:
        print(f"[AUTH] Invalid token signature: {e}")
        return None
        
import jwt from 'jsonwebtoken';

const SECRET_KEY = process.env.JWT_SECRET || 'super-secure-production-secret-key-32b';
const ALGORITHM = 'HS256';

interface TokenPayload {
  sub: string;
  role: string;
  exp?: number;
}

export function generateAccessToken(userId: string, role: string): string {
  return jwt.sign({ sub: userId, role }, SECRET_KEY, {
    algorithm: ALGORITHM,
    expiresIn: '15m',
  });
}

export function verifyAccessToken(token: string): TokenPayload | null {
  try {
    // Explicitly pass algorithms array to block alg:none attack
    return jwt.verify(token, SECRET_KEY, { algorithms: [ALGORITHM] }) as TokenPayload;
  } catch (err: any) {
    if (err.name === 'TokenExpiredError') {
      console.warn('Token expired at:', err.expiredAt);
    } else {
      console.error('Invalid token:', err.message);
    }
    return null;
  }
}
        
package main

import (
	"errors"
	"fmt"
	"time"
	"github.com/golang-jwt/jwt/v5"
)

var secretKey = []byte("super-secure-production-secret-key-32b")

type CustomClaims struct {
	Role string `json:"role"`
	jwt.RegisteredClaims
}

func GenerateToken(userID, role string) (string, error) {
	claims := CustomClaims{
		Role: role,
		RegisteredClaims: jwt.RegisteredClaims{
			Subject:   userID,
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(secretKey)
}

func VerifyToken(tokenString string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		// Enforce HS256 to prevent HMAC vs RSA key confusion attacks
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return secretKey, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, errors.New("invalid token")
}
        
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

public class JwtService {
    private static final String SECRET = "super-secure-production-secret-key-32b";
    private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));

    public static String generateToken(String userId, String role) {
        return Jwts.builder()
            .subject(userId)
            .claim("role", role)
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000))
            .signWith(KEY)
            .compact();
    }

    public static Claims verifyToken(String token) {
        return Jwts.parser()
            .verifyWith(KEY)
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
}
        
#include <jwt-cpp/jwt.h>
#include <iostream>
#include <chrono>

const std::string SECRET = "super-secure-production-secret-key-32b";

std::string generate_jwt(const std::string& user_id, const std::string& role) {
    auto token = jwt::create()
        .set_issuer("auth.systemdesign.website")
        .set_type("JWT")
        .set_subject(user_id)
        .set_payload_claim("role", jwt::claim(role))
        .set_issued_at(std::chrono::system_clock::now())
        .set_expires_at(std::chrono::system_clock::now() + std::chrono::minutes{15})
        .sign(jwt::algorithm::hs256{SECRET});
    return token;
}

bool verify_jwt(const std::string& token_str) {
    try {
        auto verifier = jwt::verify()
            .allow_algorithm(jwt::algorithm::hs256{SECRET})
            .with_issuer("auth.systemdesign.website");
        auto decoded = jwt::decode(token_str);
        verifier.verify(decoded);
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Verification failed: " << e.what() << std::endl;
        return false;
    }
}
        
๐ŸŽฏ INTERVIEW SCENARIOS

7. Security & Token Architecture Quizzes

Scenario 1: A frontend engineer suggests storing the user's JWT Access Token in localStorage so it survives browser refreshes easily. What is the primary security vulnerability of this approach?

Scenario 2: An e-commerce user clicks “Log Out All Devices” after suspecting an account breach. Since JWTs are stateless and verified without DB calls, how can you immediately invalidate all active access tokens?

โ—‹

Completed this module?

Mark as finished to update your curriculum progress tracker.