JWT Structure, Claims & Debugging Guide
Everything developers need to understand, implement, and debug JSON Web Tokens — from anatomy and claims to security pitfalls and production best practices.
JWT Anatomy: Header.Payload.Signature
A JWT is three Base64URL-encoded JSON objects concatenated with dots. It is not encrypted by default — the payload is readable by anyone who has the token. It is only signed, which ensures integrity (it hasn't been tampered with) but not confidentiality.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 ← Header
.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsImlhdCI6MTcwNDY3MjAwMCwiZXhwIjoxNzA0Njc1NjAwfQ==
← Payload (readable!)
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV... ← Signature (verifiable) Decoded Header
{
"alg": "RS256", // Algorithm: RS256, HS256, ES256, PS256
"typ": "JWT", // Token type
"kid": "key-1" // Optional: Key ID for key rotation
} Decoded Payload (Claims)
{
// Registered Claims (RFC 7519 standard)
"iss": "https://auth.yourapp.com", // Issuer
"sub": "user_123", // Subject (user ID)
"aud": "api.yourapp.com", // Audience (your API)
"exp": 1704675600, // Expiry (Unix timestamp)
"nbf": 1704672000, // Not Before
"iat": 1704672000, // Issued At
"jti": "a1b2c3d4-uuid", // JWT ID (for revocation)
// Custom Application Claims
"email": "[email protected]",
"roles": ["user", "admin"],
"plan": "pro"
} Algorithm Comparison
| Algorithm | Type | Key Setup | Use Case | Security |
|---|---|---|---|---|
| HS256 | Symmetric HMAC | Single shared secret | Single service | Good (if secret strong) |
| HS512 | Symmetric HMAC | Single shared secret | Single service (stronger) | Better |
| RS256 | Asymmetric RSA | Private + public key pair | Distributed / microservices | Strong |
| ES256 | Asymmetric ECDSA | EC key pair (P-256) | Mobile / constrained | Strong + compact |
| PS256 | RSA-PSS | Private + public key pair | High-security systems | Strongest RSA |
| none | Unsigned | None | Never in production | CRITICAL VULN |
Node.js Implementation
Sign and Verify with jsonwebtoken
import jwt from 'jsonwebtoken';
import { readFileSync } from 'fs';
const PRIVATE_KEY = readFileSync('./keys/private.pem');
const PUBLIC_KEY = readFileSync('./keys/public.pem');
// Sign a token (RS256)
function signToken(userId: string, roles: string[]): string {
return jwt.sign(
{
sub: userId,
roles,
iss: 'https://auth.yourapp.com',
aud: 'api.yourapp.com',
},
PRIVATE_KEY,
{
algorithm: 'RS256',
expiresIn: '15m', // Short-lived access token
jwtid: crypto.randomUUID(),
}
);
}
// Verify a token — always specify algorithms explicitly!
function verifyToken(token: string) {
return jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'], // ← NEVER omit this!
issuer: 'https://auth.yourapp.com',
audience: 'api.yourapp.com',
});
} Express Middleware
import { Request, Response, NextFunction } from 'express';
export function authenticate(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = authHeader.slice(7);
try {
const payload = verifyToken(token);
req.user = payload; // Attach verified claims to request
next();
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
} Python Implementation (python-jose)
from jose import jwt, JWTError
from datetime import datetime, timedelta, timezone
import uuid
SECRET_KEY = "your-256-bit-secret-from-env"
ALGORITHM = "HS256"
def create_access_token(user_id: str, roles: list[str]) -> str:
now = datetime.now(timezone.utc)
payload = {
"sub": user_id,
"roles": roles,
"iss": "https://auth.yourapp.com",
"aud": "api.yourapp.com",
"iat": now,
"exp": now + timedelta(minutes=15),
"jti": str(uuid.uuid4()),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def verify_token(token: str) -> dict:
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM], # explicit allowlist
audience="api.yourapp.com",
issuer="https://auth.yourapp.com",
)
return payload
except JWTError as e:
raise ValueError(f"Invalid token: {e}") Critical JWT Vulnerabilities
| Vulnerability | Attack | Fix |
|---|---|---|
| alg:none | Set alg to "none", strip signature | Always specify allowed algorithms |
| Algorithm confusion | Switch RS256 to HS256, sign with public key | Enforce algorithm server-side |
| Weak HS256 secret | Brute-force short/dictionary secrets | 256-bit random secret minimum |
| No expiry | Stolen token valid forever | Always set exp (15 min access) |
| Sensitive data in payload | Base64 decode reveals PII | Use JWE for encrypted tokens |
Frequently Asked Questions
What are the three parts of a JWT?▼
A JWT consists of three Base64URL-encoded parts separated by dots: Header.Payload.Signature. The header specifies the token type and signing algorithm (e.g., HS256, RS256). The payload contains claims — registered claims like 'sub', 'iat', 'exp', plus custom application claims. The signature is computed over the header and payload using the secret or private key, ensuring the token has not been tampered with.
What is the difference between HS256 and RS256?▼
HS256 (HMAC-SHA256) uses a single shared secret for both signing and verification — all parties must have the same secret, making it suitable for single-service tokens. RS256 (RSA-SHA256) uses a private key to sign and a public key to verify — the private key stays on the auth server, the public key can be shared broadly. RS256 is preferred for distributed systems and third-party token consumers. ES256 (ECDSA) gives similar asymmetric benefits with smaller key sizes.
What is the 'alg:none' JWT vulnerability?▼
The 'alg:none' attack exploits JWT libraries that accept unsigned tokens when the algorithm is set to 'none'. An attacker modifies the header to set alg to 'none', removes the signature, and the server accepts the token as valid. Mitigation: always explicitly specify which algorithms your application accepts and reject any token with alg:none. Never trust the algorithm from the token header — enforce it server-side.
What JWT claims should I always validate?▼
Always validate: 'exp' (expiry — reject expired tokens), 'nbf' (not before — reject tokens not yet valid), 'iss' (issuer — must match your expected auth server), 'aud' (audience — must match your application identifier). Optional but recommended: 'jti' (JWT ID for revocation tracking), 'sub' (subject matches the user). Libraries like jsonwebtoken, python-jose, and java-jwt validate these automatically when you pass the expected values.
Should I store JWTs in localStorage or cookies?▼
httpOnly cookies are the safer choice. localStorage is accessible to JavaScript, making JWTs stored there vulnerable to XSS attacks — any script that runs on your page can read them. httpOnly cookies cannot be read by JavaScript and are automatically sent by the browser. Use Secure + SameSite=Strict or SameSite=Lax cookies. The tradeoff: cookies require CSRF protection (use double-submit cookie pattern or SameSite=Strict). For SPAs calling third-party APIs, in-memory storage (React state) is another XSS-resistant option.
How do I revoke a JWT before it expires?▼
JWTs are stateless by design, which makes revocation difficult. Approaches: (1) Short expiry + refresh tokens — issue 15-minute access tokens; revoke the refresh token. (2) Token blocklist — maintain a Redis/DB set of revoked 'jti' values; check on each request. (3) Token version — store a 'token_version' on the user record; reject tokens issued before a version increment. Each approach trades statelessness for revocation capability. Most production systems use refresh tokens with short-lived access tokens.
Related Products
on GumroadDevPlaybook Interview Vault
210+ real technical interview questions with model answers, asked at Google, Meta, Amazon, Microsoft, Stripe, and Shopify. Covers Frontend (55), Backend (55), System Design (50), and DSA (50). Available in English and Traditional Chinese.
Buy on Gumroad →TypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad →Developer Interview Vault
210 technical interview questions with model answers, key points, and follow-up questions. Covers Frontend, Backend, System Design, and DSA — bilingual English + Traditional Chinese (ZH-TW). PWYW from $29.
Buy on Gumroad →You Might Also Need
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
API Response Mock Generator
Generate realistic mock API responses with custom status codes, headers, and JSON body. Quick templates for 200, 401, 404, 429, 500 and more. Runs in your browser.
Open free tool →
API Response Time Tester
Test API endpoint response times directly from your browser. Run multiple requests, see average/min/max times, status codes, and response sizes. GET, POST, PUT, DELETE support. Free.
Open free tool →
More Free Tools
Related Articles
API Rate Limiting: Algorithms and Implementation Guide 2026
Complete guide to API rate limiting in 2026. Token Bucket, Leaky Bucket, Sliding Window algorithms with Node.js and Redis implementations, distributed rate limiting, 429 handling, and best practices.
API Rate Limiting: Strategies, Algorithms, and Free Tools
A complete guide to API rate limiting in 2025: Fixed Window, Sliding Window, Token Bucket, and Leaky Bucket algorithms explained, with implementation in Node.js, Python, and Go, Redis-based distributed limiting, and client-side backoff strategies.
The Ultimate API Design Checklist for Production Apps
Ship production-ready APIs with this comprehensive checklist. Covers REST design, authentication, validation, error handling, rate limiting, versioning, documentation, and monitoring.
FastAPI vs Django REST Framework 2025: Which Should You Choose?
FastAPI vs Django REST Framework compared for 2025. Performance benchmarks, feature comparison, real-world use cases, and a clear decision guide for your next Python API project.
JWT Authentication Best Practices 2026: Secure Your API Tokens
Complete JWT security guide for 2026. Covers RS256 vs HS256 signing, token storage strategies, expiry and refresh token patterns, revocation, common vulnerabilities, and a production security checklist.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →