1JWT Structure — Header.Payload.Signature
A JSON Web Token is a compact, URL-safe string composed of exactly three Base64Url-encoded parts separated by dots: HEADER.PAYLOAD.SIGNATURE. Each part encodes specific data and plays a distinct role in the security architecture.
| Component | Function & Security Rule |
|---|---|
| Header | Specifies the signing algorithm (alg) and token type (typ). Decoded by anyone. Never encrypt secrets here. |
| Payload | Contains the claims — user data, permissions, expiry times. Visible to everyone. Base64Url encoded, not encrypted. |
| Signature | Cryptographic proof that the header and payload were not tampered with. Requires the secret key to generate or verify. |
The signature is computed as ALG(base64url(header) + "." + base64url(payload), secret). If anyone modifies even a single character in the header or payload, the signature instantly becomes invalid — without the secret key, tampering is detectable.
2The Base64 Misconception — Encoded ≠ Encrypted
The most dangerous JWT security misconception is that tokens hide payload data. They do not. Anyone who intercepts or receives the token string can instantly decode the payload using nothing more than a Base64 decoder.
What Base64Url encoding provides: a URL-safe, compact, text-friendly transport format for the JSON structures. It is reversible transformation, not protection. The only security guarantee in a standard JWT is the signature — proving the token was issued by someone who knows the secret, and that it was not modified in transit.
3Algorithm Guide — HMAC, RSA & ECDSA
HS256 / HS384 / HS512 — HMAC (Symmetric)
HMAC algorithms use a single shared secret key for both creating and verifying tokens. Extremely fast. Simple to implement. Fatal flaw: every microservice that needs to verify tokens must possess the secret — and any service that can verify can also forge tokens. Best for single-server or trusted monolithic environments.
RS256 / RS384 / RS512 — RSA (Asymmetric)
RSA uses a key pair: a private key held only by the auth server to sign tokens, and a public key distributed to all verifying services. Services can verify tokens without being able to forge them. The gold standard for OAuth2/OIDC systems (Auth0, Okta, Google). Disadvantage: larger tokens and slower signing compared to HMAC.
/.well-known/jwks.json endpoint and rotate it regularly without touching your services.
ES256 / ES384 / ES512 — ECDSA (Asymmetric)
ECDSA provides the same asymmetric guarantees as RSA but with much smaller keys and signatures. ES256 uses the P-256 elliptic curve — its 32-byte signature is 8× smaller than an equivalent RS256 signature. Ideal for environments where token size matters (mobile apps, IoT, CDN edge functions). ES256 is now preferred over RS256 in modern auth systems.
4Standard Claims — RFC 7519 Reference
JWT payloads use compact 3-letter claim names from RFC 7519 to minimise token size. The Claims Inspector tab fully parses all of these automatically:
| Claim | Name | Usage & Security Rules |
|---|---|---|
iss | Issuer | Who created and signed the token. Case-sensitive URI, e.g., https://auth.example.com. |
sub | Subject | The principal the token represents (usually user ID). Unique within the issuer context. |
aud | Audience | Intended recipient(s). Must match the verifying application's identifier exactly. |
exp | Expiration | Unix timestamp after which the token must be rejected. Always set this. |
nbf | Not Before | Token must not be accepted before this Unix timestamp. Guards against clock-skew abuse. |
iat | Issued At | Unix timestamp of token creation. Used to calculate token age and enforce maximum lifetimes. |
jti | JWT ID | Unique identifier for this specific token. Enables replay attack prevention via server-side blocklist. |
5Common JWT Attacks
1. Algorithm Confusion (alg:none)
An attacker crafts a token with "alg":"none" and removes the signature. Vulnerable backends that blindly trust the algorithm field will accept the unsigned, forged token. Fix: Always whitelist allowed algorithms server-side. Never trust the alg header value without validation.
2. Algorithm Substitution (RS256 → HS256)
If a server supports both RS256 and HS256, an attacker may forge an HS256 token signed with the public RSA key (which is publicly known). The server mistakenly verifies it using the public key as an HMAC secret. Fix: Pin the expected algorithm in your verification code. Do not accept multiple algorithms for the same endpoint.
3. Weak Secret Brute Force
Short or common HMAC secrets can be brute-forced offline. An attacker who captures a valid token can attempt millions of secret candidates per second. Fix: Use cryptographically random secrets of at least 256 bits (32 bytes) for HS256, 384 bits for HS384, 512 bits for HS512. Never use dictionary words or passwords.
4. JWT Replay After Expiry
Without a server-side blocklist and a jti claim, stolen valid tokens can be replayed even after the user logs out. Fix: Implement token revocation — maintain a Redis-backed blocklist of invalidated jti values, or use short token lifetimes (≤15 minutes) with refresh token rotation.
6JWS vs. JWE — When to use Encryption
The standard JWT you are familiar with is technically a JWS (JSON Web Signature). It guarantees data integrity but provides absolutely zero privacy. Anyone can decode it. If you must transmit highly sensitive Personally Identifiable Information (PII) or secrets through an untrusted medium, you must use a JWE (JSON Web Encryption).
| Format | Primary Purpose | Structure | Use Case |
|---|---|---|---|
| JWS | Authentication & Integrity | Header.Payload.Signature | Standard stateless user sessions (OAuth2 / OIDC). |
| JWE | Confidentiality & Privacy | Header.EncKey.Iv.Ciphertext.Tag | Transmitting sensitive health data or API secrets. |
7JWT Best Practices Checklist
- Always set
exp— access tokens ≤15 min, API keys ≤24h, refresh tokens ≤30 days with rotation - Include
jti— unique UUID per token, maintain revocation blocklist - Set
aud— explicitly name the target API to prevent token reuse across services - Use RS256 or ES256 for distributed systems — HMAC is only safe in single-trust-boundary systems
- Use HTTPS only — a JWT in transit over HTTP is completely exposed to network sniffers
- Store tokens securely — HttpOnly cookies preferred over localStorage (immune to XSS exfiltration)
- Never put secrets in the payload — payload is publicly readable by any token holder
- Validate all claims — verify
iss,aud,exp,nbfon every request - Whitelist algorithms — hardcode the expected algorithm; never trust the header's
algfield alone - Rotate secrets regularly — for HMAC, rotate at least every 90 days; for RSA/EC, rotate annually
8Token Size Limits & Performance Impact
Because JWTs are stateless, they must carry all necessary authorization data (like user roles and permissions) on every single HTTP request. This creates a critical tradeoff between statefulness and network bandwidth.
The 8KB Proxy Threshold
While the JWT specification (RFC 7519) does not define a maximum size limit, the physical infrastructure of the internet does. Most load balancers, proxies (like Nginx, HAProxy), and CDNs (Cloudflare, AWS CloudFront) enforce strict limits on HTTP header sizes — typically around 8KB (8,192 bytes). If your Authorization: Bearer <token> header exceeds this limit, the proxy will reject the request with a 431 Request Header Fields Too Large error before it ever reaches your application.
role_id or session_id and fetch the heavy metadata server-side using a high-speed cache like Redis.
9Implementing Refresh Token Rotation Safely
To mitigate the risk of stolen access tokens, modern auth architectures rely on Refresh Token Rotation (RTR). Instead of issuing a single long-lived refresh token, RTR issues a new refresh token every time the client requests a new access token.
How RTR Prevents Token Theft
When a client uses a refresh token (Token A) to get a new access token, the server invalidates Token A and issues Token B. If an attacker stole Token A and tries to use it after the legitimate client already used it, the server detects the reuse of an invalidated token. The server immediately assumes a breach has occurred and revokes the entire token family, forcing the legitimate user to re-authenticate and locking the attacker out.
| Storage Mechanism | Security Status | Vulnerability |
|---|---|---|
| localStorage | High Risk | Vulnerable to Cross-Site Scripting (XSS) attacks. Any malicious script on the page can steal the tokens. |
| sessionStorage | Moderate Risk | Still vulnerable to XSS, but tokens are cleared when the browser tab is closed. |
| HttpOnly Cookies | Most Secure | Immune to XSS. JavaScript cannot read the token. Must be combined with SameSite=Strict to prevent CSRF. |
10Debugging "Invalid Signature" Errors
When your backend server rejects a JWT with an "Invalid Signature" or "Signature Verification Failed" error, it can be notoriously difficult to debug because cryptography provides zero feedback on why the math failed. Here is the definitive diagnostic checklist:
- Mismatched Secrets: Ensure your verifying server is using the exact same HMAC secret string or RSA public key that the issuing server used. A single character difference causes total failure.
- Encoding Mismatches: Is your secret Base64 encoded in your environment variables, but your code is reading it as a raw string? Ensure both the issuer and verifier handle the secret's encoding format identically (e.g., parsing a Hex or Base64 string into a byte array before hashing).
- Trailing Whitespace: A common issue when loading PEM public keys from
.envfiles. Trailing spaces or missing newline characters in the PEM header/footer (-----BEGIN PUBLIC KEY-----) will corrupt the key buffer. - Algorithm Mismatch: The server expects
RS256but the token header saysHS256. (This studio's 12-Point Audit flags this as a Key Confusion vulnerability).