JWT Deep Dive: Structure, Signing, & Critical Pitfalls
Master JSON Web Tokens: Base64URL anatomy, symmetric HS256 vs asymmetric RS256/ES256, JWKS key rotation, the "alg: none" vulnerability, RSA-to-HMAC key confusion attacks, and JWS vs JWE differences.
JWT Anatomy & Signature Attack Lab
Forge tokens against three classic exploits, then harden the verifier's algorithm whitelist to kill them.
{"alg":"RS256","typ":"JWT","kid":"auth-key-2026-v1"}{"sub":"usr_98124","role":"user","iss":"https://auth.company.com","exp":1774889200}Signature recomputed from header.payload matches — claims trusted.
Attack scenario
JWS ≠ encryption. Every byte above is public base64url — never put raw passwords or PII in claims (use JWE if confidentiality is required).
Defenses to state in interviews: whitelist algorithms, pin iss/aud, pick kid from JWKS only for the expected alg, prefer ES256/Ed25519 over RS256 for smaller keys at equal strength.
Signature here is a deterministic demo digest standing in for real RSA/HMAC math.
JWT Structure & Asymmetric JWKS Verification 📜
Deconstructing the 3-part Base64URL token and asymmetric signature verification across microservices.
01.Anatomy of a JSON Web Token (JWS)
A standard JSON Web Signature (JWS) token consists of three Base64URL-encoded JSON segments separated by literal periods (.): header.payload.signature.
1. Header (Metadata):
Defines the cryptographic algorithm and key metadata:
json{ "alg": "RS256", "typ": "JWT", "kid": "auth-key-2026-v1" }
alg: The cryptographic signing algorithm (e.g.,RS256= RSA Signature with SHA-256;ES256= ECDSA with P-256 and SHA-256;HS256= HMAC-SHA256).typ: Token type, typically"JWT".kid: Key Identifier used by the verifying service to select the correct public key from a JWKS set.
2. Payload (Claims):
Contains statements about the subject entity and contextual metadata:
- Registered Claims (RFC 7519 standard):
iss(Issuer): Who created and signed the token (e.g.,https://auth.company.com/).sub(Subject): Unique principal ID (e.g.,usr_981442).aud(Audience): Who the token is intended for (e.g.,https://api.company.com/billing).exp(Expiration Time): Unix epoch timestamp after which the token must be rejected.nbf(Not Before): Unix epoch timestamp before which the token is invalid.iat(Issued At): Unix epoch timestamp of token generation.jti(JWT ID): Unique identifier for the token (used to detect replay attacks).
- Custom / Application Claims: Arbitrary business data (e.g.,
roles: ["admin", "billing_manager"],tenant_id: "acme_corp").
3. Signature:
Guarantees integrity and authenticity. For RS256:
Signature = Sign_{PrivateKey}(Base64URL(Header) + "." + Base64URL(Payload))
02.Symmetric (HS256) vs Asymmetric (RS256 / ES256) Signing
Choosing the right signing algorithm is the single most critical architectural decision when designing JWT systems:
Symmetric (HMAC-SHA256 / HS256):
- Mechanism: Uses a single shared symmetric secret key (e.g., a 256-bit random hex string) for both signing and verifying.
- The Microservice Danger: If 50 microservices need to verify user tokens, all 50 services must possess the shared secret. If a single low-security microservice is compromised, the attacker extracts the secret and can forge valid tokens for any user or administrator across the entire ecosystem.
Asymmetric (RS256 / ES256 / Ed25519):
- Mechanism: Uses a public/private keypair.
- Private Key: Held exclusively by the centralized Identity Provider (Auth0, Okta, internal Auth Service). Only the IdP can sign tokens.
- Public Key: Published openly via a JSON Web Key Set (JWKS) endpoint (e.g.,
https://auth.example.com/.well-known/jwks.json).
- Zero Leakage Risk: Downstream microservices only download the public key. Even if an attacker compromises a microservice, they cannot forge signatures.
- Modern Recommendation: Prefer ES256 (ECDSA) or Ed25519 over RS256 for faster computation (
< 0.1ms) and smaller key sizes (256 bits vs 2048/4096 bits for equivalent cryptographic strength).
03.Critical JWT Security Pitfalls & Exploits
Numerous high-severity CVEs in major JWT libraries stem from improper signature validation logic:
1. The "alg": "none" Vulnerability (CVE-2015-9235):
The JWT specification allows an algorithm header value of "none" for unsecure tokens. Attackers strip the signature, change "alg": "RS256" to "none", alter the payload to "role": "admin", and submit the token. Vulnerable libraries accept the forged payload without checking the signature.
- Defense: Explicitly configure JWT verification libraries to whitelist allowed algorithms:
algorithms: ['RS256', 'ES256']and rejectnone.
2. The RSA-to-HMAC Key Confusion Attack (CVE-2016-5431):
If an application supports both RS256 and HS256, an attacker takes the server's public RSA key (which is publicly available on the web) and uses it as the HMAC secret to sign a forged token with "alg": "HS256". If the backend parser uses a generic verify function that accepts any algorithm indicated in the token header, it verifies the signature using the RSA public key string with HMAC, which matches and passes validation!
- Defense: Never determine the verification algorithm from the incoming token's unverified
algheader. Hardcode or strictly enforce the expected algorithm in the verifier.
3. JWS vs JWE (Signing vs Encryption):
- JWS (Signed JWT): Encoded in plaintext Base64URL. Anyone who intercepts the token can read all payload fields. Never store raw passwords, social security numbers, or sensitive API secrets in JWT claims.
- JWE (Encrypted JWT): Encrypts the payload so that only holders of the decryption key can read the claims. Used when claims contain sensitive personal data.
04.Production TypeScript Verification with JWKS Caching
Below is a robust Node.js implementation using jwks-rsa and jsonwebtoken that securely fetches and caches public keys while guarding against known vulnerabilities:
typescriptimport jwt, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken'; import jwksClient from 'jwks-rsa'; // Configure secure JWKS client with in-memory caching & rate-limiting const client = jwksClient({ jwksUri: 'https://auth.company.com/.well-known/jwks.json', cache: true, cacheMaxEntries: 10, cacheMaxAge: 60 * 60 * 1000, // 1 hour TTL rateLimit: true, jwksRequestsPerMinute: 10 // Prevent DoS on JWKS endpoint }); function getKey(header: JwtHeader, callback: SigningKeyCallback): void { if (!header.kid) { return callback(new Error('Missing kid (Key ID) header in JWT')); } client.getSigningKey(header.kid, (err, key) => { if (err || !key) { return callback(err || new Error('Signing key not found in JWKS')); } const signingKey = key.getPublicKey(); callback(null, signingKey); }); } export interface ValidatedUserClaims { sub: string; role: string; tenantId: string; } export function verifyUserToken(token: string): Promise<ValidatedUserClaims> { return new Promise((resolve, reject) => { jwt.verify( token, getKey, { // DEFENSE: Strictly whitelist expected algorithms (prevents "none" and key confusion) algorithms: ['RS256'], issuer: 'https://auth.company.com/', audience: 'https://api.company.com/' }, (err, decoded) => { if (err) { return reject(new Error(`Token verification failed: ${err.message}`)); } resolve(decoded as ValidatedUserClaims); } ); }); }
Architectural Trade-offs & Production Realities
Architectural Advantages
- RS256/ES256 with JWKS enables seamless, decentralized verification across polyglot microservices without sharing secrets
- Standardized claims (iss, sub, aud, exp, iat) simplify cross-organizational federation
- Public key rotation via JWKS requires zero deployment downtime on downstream services
Trade-offs & Constraints
- Payload claims are visible to anyone in plaintext; sensitive PII must not be stored in standard JWS tokens
- Misconfigured verification libraries that dynamically trust the "alg" header are susceptible to critical exploits (alg: none, key confusion)
- JWT tokens are bulky compared to 32-byte opaque tokens, increasing HTTP header overhead on every request
When a GitHub Actions workflow deploys to AWS, GitHub generates a short-lived (10-minute) RS256 JWT. AWS IAM verifies the token against GitHub's public JWKS endpoint (`https://token.actions.githubusercontent.com/.well-known/jwks.json`) and exchanges it for temporary AWS STS credentials, completely eliminating hardcoded AWS secret keys from GitHub repositories.
Staff+ Engineering Takeaways
- JWT consists of Header, Payload, and Signature in Base64URL encoding.
- Always use asymmetric algorithms (RS256 / ES256) for microservice ecosystems.
- Strictly whitelist verification algorithms to eliminate "alg: none" and RSA-to-HMAC key confusion attacks.
- Never store sensitive confidential secrets in standard JWS claims; they are readable by anyone.
Topic Knowledge Check
Exercise 1 of 2 • Test your architectural comprehension.
What is the root vulnerability underlying the RSA-to-HMAC "Key Confusion" attack in JWT validation libraries?
How clear and actionable was this distributed systems breakdown?