1. The Three Parts of a JWT (JWS Compact Serialization)
A JSON Web Token is a compact string consisting of three distinct segments separated by periods (.):
header.payload.signature
2. Critical Distinction: Signed vs. Encrypted
A standard JWT is a JSON Web Signature (JWS), not a JSON Web Encryption (JWE).
The payload is NOT encrypted. It is simply Base64URL encoded. Anyone who intercepts the token can paste it into our JWT Decoder and read every claim, email address, role, and permission. Never place secret passwords, private keys, or unencrypted personally identifiable information (PII) inside a JWT payload.
3. The Infamous "alg: none" Attack
In early JWT library implementations, the specification allowed an algorithm value of "none" for unsigned tokens. Malicious actors exploited this by taking a legitimate token, changing the payload claim from "admin": false to "admin": true, setting "alg": "none" in the header, and stripping the signature entirely.
Vulnerable verification libraries would read the header, see "none", skip cryptographic signature verification, and grant the attacker full administrative access!
// ✅ Production rule: Explicitly lock down accepted verification algorithms
jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'] // Reject 'none', 'RS256', and unexpected algorithms!
});4. The Algorithm Confusion Attack (RSA to HMAC)
When an application uses asymmetric encryption (e.g., RS256), the server signs the token using its private key, and any service verifies it using the public key.
If the backend verification code is written carelessly:
// ❌ Vulnerable to algorithm substitution: jwt.verify(token, serverPublicKey);
An attacker can download the server's public key (which is public!), change the token header from RS256 to HS256 (symmetric HMAC), sign the forged token using the public key as the HMAC secret, and send it to the server. The server verifies the HMAC signature using its own public key and accepts the forged token!
5. Token Storage: LocalStorage vs. HttpOnly Cookies
Where should you store JWTs in single-page applications?
- LocalStorage: Vulnerable to Cross-Site Scripting (XSS). Any compromised third-party npm package, CDN script, or injected script can read
localStorage.getItem('token')and exfiltrate your session token. - HttpOnly, Secure, SameSite Cookies: Immune to JavaScript inspection. JavaScript in the browser cannot read HttpOnly cookies. To prevent Cross-Site Request Forgery (CSRF), set
SameSite=LaxorSameSite=Strict.
6. Short Expirations & Refresh Token Rotation
Because JWTs are stateless, you cannot easily revoke a token before its expiration (exp) without introducing a stateful database blacklist.
The industry gold standard:
- Issue a short-lived Access Token (valid for 5 to 15 minutes) kept in memory or cookie.
- Issue a long-lived Refresh Token (valid for 7 to 30 days) stored in an HttpOnly cookie with database rotation.
- When refreshing tokens, invalidate the old refresh token immediately. If a previously consumed refresh token is ever submitted, immediately revoke the entire user session (detecting token theft).