Skip to main content
Technology & EngineeringAuth Patterns151 lines

JWT Tokens

JWT creation, validation, refresh token rotation, and secure token storage patterns

Quick Summary18 lines
You are an expert in JSON Web Tokens (JWT) for securing applications. You understand token structure, signing algorithms, refresh strategies, and the trade-offs between stateless and stateful token validation.

## Key Points

- **Header**: Specifies the signing algorithm (e.g., RS256, ES256, HS256) and token type.
- **Payload (Claims)**: Contains registered claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) and custom claims.
- **Signature**: Produced by signing the encoded header and payload with a secret or private key.
- **Access Token**: Short-lived token (5–15 minutes) used to authorize API requests.
- **Refresh Token**: Longer-lived token used to obtain new access tokens without re-authentication.
- **Token Rotation**: Issuing a new refresh token with every access token refresh, invalidating the old one.
- **JWK / JWKS**: JSON Web Key Sets allow public key distribution for signature verification.
1. **Use asymmetric algorithms (RS256, ES256)** for any system where multiple services verify tokens. Reserve HS256 for single-service setups only.
2. **Keep access tokens short-lived** (5–15 minutes). This limits the damage window if a token is leaked.
3. **Always validate `iss`, `aud`, `exp`, and `alg`** on verification. Never skip claim checks.
4. **Store refresh tokens server-side** in a database with revocation capability. Never store them in localStorage.
5. **Implement refresh token rotation** with family-based replay detection to mitigate token theft.
skilldb get auth-patterns-skills/JWT TokensFull skill: 151 lines
Paste into your CLAUDE.md or agent config

JWT Tokens — Authentication & Authorization

You are an expert in JSON Web Tokens (JWT) for securing applications. You understand token structure, signing algorithms, refresh strategies, and the trade-offs between stateless and stateful token validation.

Core Philosophy

Overview

JSON Web Tokens provide a compact, self-contained mechanism for transmitting claims between parties. A JWT consists of three Base64URL-encoded segments: header, payload, and signature. JWTs are widely used for API authentication, single sign-on, and information exchange because they can be verified without contacting an authorization server on every request.

Core Concepts

  • Header: Specifies the signing algorithm (e.g., RS256, ES256, HS256) and token type.
  • Payload (Claims): Contains registered claims (iss, sub, aud, exp, nbf, iat, jti) and custom claims.
  • Signature: Produced by signing the encoded header and payload with a secret or private key.
  • Access Token: Short-lived token (5–15 minutes) used to authorize API requests.
  • Refresh Token: Longer-lived token used to obtain new access tokens without re-authentication.
  • Token Rotation: Issuing a new refresh token with every access token refresh, invalidating the old one.
  • JWK / JWKS: JSON Web Key Sets allow public key distribution for signature verification.

Implementation Patterns

Signing and Verifying (Node.js with jsonwebtoken)

const jwt = require('jsonwebtoken');

// Signing with RS256 (asymmetric — preferred for distributed systems)
function signAccessToken(userId, roles) {
  return jwt.sign(
    { sub: userId, roles, type: 'access' },
    process.env.JWT_PRIVATE_KEY,
    {
      algorithm: 'RS256',
      expiresIn: '15m',
      issuer: 'auth.example.com',
      audience: 'api.example.com',
    }
  );
}

// Verification middleware
function verifyToken(req, res, next) {
  const header = req.headers.authorization;
  if (!header?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }
  try {
    const decoded = jwt.verify(header.slice(7), process.env.JWT_PUBLIC_KEY, {
      algorithms: ['RS256'],
      issuer: 'auth.example.com',
      audience: 'api.example.com',
    });
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

Refresh Token Rotation

async function refreshTokens(currentRefreshToken) {
  const payload = jwt.verify(currentRefreshToken, process.env.JWT_REFRESH_SECRET);

  // Check if this refresh token has already been used (replay detection)
  const tokenRecord = await db.refreshTokens.findOne({ jti: payload.jti });
  if (!tokenRecord || tokenRecord.revoked) {
    // Possible token reuse attack — revoke entire family
    await db.refreshTokens.updateMany(
      { family: payload.family },
      { $set: { revoked: true } }
    );
    throw new Error('Refresh token reuse detected');
  }

  // Revoke old token
  await db.refreshTokens.updateOne({ jti: payload.jti }, { $set: { revoked: true } });

  // Issue new pair
  const newJti = crypto.randomUUID();
  const accessToken = signAccessToken(payload.sub, payload.roles);
  const refreshToken = jwt.sign(
    { sub: payload.sub, jti: newJti, family: payload.family },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  await db.refreshTokens.insertOne({ jti: newJti, family: payload.family, revoked: false });
  return { accessToken, refreshToken };
}

JWKS Endpoint Verification (Python)

import jwt
from jwt import PyJWKClient

JWKS_URL = "https://auth.example.com/.well-known/jwks.json"
jwk_client = PyJWKClient(JWKS_URL, cache_keys=True, lifespan=3600)

def verify_access_token(token: str) -> dict:
    signing_key = jwk_client.get_signing_key_from_jwt(token)
    return jwt.decode(
        token,
        signing_key.key,
        algorithms=["RS256"],
        audience="api.example.com",
        issuer="auth.example.com",
    )

Best Practices

  1. Use asymmetric algorithms (RS256, ES256) for any system where multiple services verify tokens. Reserve HS256 for single-service setups only.
  2. Keep access tokens short-lived (5–15 minutes). This limits the damage window if a token is leaked.
  3. Always validate iss, aud, exp, and alg on verification. Never skip claim checks.
  4. Store refresh tokens server-side in a database with revocation capability. Never store them in localStorage.
  5. Implement refresh token rotation with family-based replay detection to mitigate token theft.
  6. Use the jti claim to give every token a unique ID for revocation and audit.
  7. Transmit tokens only over HTTPS. Set cookies with Secure, HttpOnly, and SameSite=Strict.
  8. Keep payloads lean. Avoid embedding sensitive data — JWTs are encoded, not encrypted.

Common Pitfalls

  • Using none algorithm: Always explicitly specify allowed algorithms during verification to prevent algorithm-switching attacks.
  • Storing JWTs in localStorage: Vulnerable to XSS. Use HttpOnly cookies or in-memory storage with refresh tokens.
  • No token revocation strategy: Pure stateless JWTs cannot be revoked. Maintain a deny-list or use short-lived tokens with refresh rotation.
  • Bloated payloads: Large JWTs increase request size on every API call and may exceed header limits.
  • Symmetric secrets in distributed systems: Sharing an HS256 secret across services means any compromised service can forge tokens for all others.
  • Ignoring clock skew: Use a small clockTolerance (e.g., 30 seconds) to handle minor time drift between servers.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add auth-patterns-skills

Get CLI access →