Skip to main content

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.

✓ Token anatomy breakdown ✓ HS256 vs RS256 vs ES256 ✓ Common vulnerabilities ✓ Node.js & Python examples

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
HS256Symmetric HMACSingle shared secretSingle serviceGood (if secret strong)
HS512Symmetric HMACSingle shared secretSingle service (stronger)Better
RS256Asymmetric RSAPrivate + public key pairDistributed / microservicesStrong
ES256Asymmetric ECDSAEC key pair (P-256)Mobile / constrainedStrong + compact
PS256RSA-PSSPrivate + public key pairHigh-security systemsStrongest RSA
noneUnsignedNoneNever in productionCRITICAL 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:noneSet alg to "none", strip signatureAlways specify allowed algorithms
Algorithm confusionSwitch RS256 to HS256, sign with public keyEnforce algorithm server-side
Weak HS256 secretBrute-force short/dictionary secrets256-bit random secret minimum
No expiryStolen token valid foreverAlways set exp (15 min access)
Sensitive data in payloadBase64 decode reveals PIIUse 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.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

OpenAPI Validator API Response Mock Generator API Response Time Tester OpenAPI Schema Validator OAuth2 Flow Visualizer OpenAPI Mock Generator OAuth2 Flow Debugger API Test Case Generator API Request Builder HTTP Security Headers Generator OAuth2 Flow Tester API Mock Server Config Generator HTTP Security Headers Checker authentik API Rate Limit Calculator API Response Formatter & Analyzer HTTP API Tester LLM Cost Calculator AI Prompt Cost Calculator REST API Request Builder Content Security Policy Builder LLM Prompt Cost Calculator API Response Mocker HTTP Header Inspector AI Rate Limit Calculator Prompt Injection Tester GCP Service Account Key Auditor HTTP Headers Analyzer API Key Generator AI Doc Generator — Pro Tool AI System Guardian .htaccess Generator HTTP Methods Reference HTTP Code Snippet Generator HTTP Status Code Reference HTTP Status Code Reference Interview Question Generator JSON Diff Viewer JSON Diff nginx Config Generator Password Strength Checker Rate Limiter Tester API Changelog Generator HTTP Mock Response Builder HMAC Generator CSP Header Builder Rate Limit Calculator CORS Policy Builder AWS IAM Policy Validator Haproxy Config Validator HTTP Cookie Parser SBOM Generator HTTP Status Code Lookup TOTP Code Generator HTTP Status Code Lookup Vue Component Generator HTTP Status Code Reference LLM Context Window Calculator HTTP Header Analyzer LLM Token Counter Dockerfile Linter mTLS Certificate Chain Builder Prompt Token Estimator Mock Service Worker (MSW) API Mocking Guide Pulumi Modern IaC Guide HashiCorp Vault Secrets Management Guide AES Encryptor / Decryptor Casdoor Docker Layer Optimizer HTML5 Canvas API Playground Token Context Calculator GraphQL Client Configurator Axios Interceptor Builder OpenAI Function Calling Generator Token Counter & Cost Estimator AI API Rate Limit Calculator Password Strength Analyzer

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

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

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →