JWT Security Best Practices for Developers
A practical guide to avoiding common JWT vulnerabilities and implementing tokens securely in production systems.
Why JWT Security Matters
JWTs are the backbone of authentication in modern web applications. A compromised token gives an attacker full access to everything that token authorizes -- often an entire user account. Unlike a session cookie that the server can immediately invalidate, a stolen JWT remains valid until it expires. This makes getting your JWT security right from the start critically important.
This guide covers the most common JWT vulnerabilities researchers have discovered, followed by concrete best practices for avoiding them. If you are new to JWTs, start with our introduction to JSON Web Tokens first.
Common JWT Vulnerabilities
The “alg: none” Attack
The most famous JWT vulnerability. The JWT specification allows an algorithm value of none for unsecured JWTs -- tokens that have no signature at all. If a server library naively trusts the alg header from the token, an attacker can:
- Take a valid JWT and decode its payload
- Modify the claims (e.g., change their user ID to an admin ID)
- Set the header to
{"alg":"none"} - Remove the signature (leaving a trailing dot)
- Submit the modified token to the server
If the server accepts alg: none, it skips signature verification entirely and trusts the modified payload.
Prevention: Never accept alg: none in production. Maintain an explicit allowlist of algorithms your server accepts and reject any token whose alg header is not on the list.
Algorithm Confusion Attacks
When a server is configured to verify tokens with an RSA public key (asymmetric), an attacker might craft a token with alg: HS256 (symmetric) and sign it using the server's public key as the HMAC secret. If the server blindly trusts the alg header and uses the “key” (the RSA public key) for HMAC verification, the forged signature will validate.
Prevention: Always specify the expected algorithm in your verification code. Never let the token dictate which algorithm to use. Modern JWT libraries support explicit algorithm whitelisting.
Weak Signing Secrets
HMAC algorithms (HS256, HS384, HS512) derive their security from the shared secret. If the secret is short, predictable, or commonly used (like secret, password, or your-256-bit-secret), an attacker can brute-force it. Tools like jwt-cracker can test millions of potential secrets per second.
Prevention: Use a cryptographically random secret with at least 256 bits (32 bytes) of entropy for HS256, 384 bits for HS384, and 512 bits for HS512. Never hardcode secrets in source code.
Long-Lived Tokens
A JWT with a 30-day expiration (or worse, no expiration at all) is effectively a long-term credential. If it is stolen through XSS, a compromised device, or a log leak, the attacker has prolonged access. Since standard JWTs cannot be individually revoked without infrastructure changes, long-lived tokens amplify the blast radius of any security incident.
Prevention: Keep access token lifetimes short -- 15 minutes is a common recommendation. Use refresh tokens (stored securely in HTTP-only cookies) to issue new access tokens when the old ones expire.
Storing Tokens in localStorage
localStorage is accessible to any JavaScript running on the page, including injected scripts from XSS vulnerabilities. Storing JWTs in localStorage means any XSS attack automatically yields the authentication token.
Prevention: Store tokens in HTTP-only, Secure, SameSite cookies whenever possible. If you must use localStorage (e.g., for a SPA calling a cross-origin API), keep token lifetimes very short and implement robust XSS protections.
Best Practices
1. Always Validate Algorithm and Key
Specify the expected algorithm explicitly when verifying tokens. Here is a secure verification pattern in Node.js:
// GOOD: Algorithm explicitly specified
jwt.verify(token, publicKey, {
algorithms: ['RS256'], // Only accept RS256
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
});
// BAD: Algorithm determined from token header
jwt.verify(token, key); // Don't do this2. Set Short Expiration Times
Access tokens should expire quickly. A common pattern is:
- Access tokens: 5-15 minutes
- Refresh tokens: 7-30 days (stored securely, ideally in HTTP-only cookies)
- One-time tokens (email verification, password reset): 10-60 minutes
Short-lived access tokens limit the damage window if a token is compromised. The refresh token pattern provides a good balance between security and user experience.
3. Use Asymmetric Algorithms for Distributed Systems
Symmetric algorithms (HS256) require sharing the same secret between the token issuer and all verifiers. In a microservices architecture, this means distributing a secret to every service, increasing the attack surface. Asymmetric algorithms (RS256, ES256) are a better choice: the auth server holds the private key, and all other services only need the public key.
For detailed guidance on choosing an algorithm, see our JWT Signing Algorithms Explained guide.
4. Validate All Relevant Claims
Beyond just verifying the signature, always validate:
exp: Reject expired tokens. Include a small clock skew tolerance (e.g., 30 seconds) to account for clock differences between servers.iss: Verify the token was issued by your trusted auth server.aud: Verify the token was intended for your service. This prevents a token issued for Service A from being used on Service B.nbf: If present, reject tokens that are not yet valid.
5. Implement Token Revocation
Standard JWTs are stateless and cannot be individually revoked. However, you can implement revocation when needed:
- Token blocklist: Maintain a list of revoked token IDs (
jticlaim) in a fast datastore like Redis. Check against this list on each request. - Token version: Store a version counter per user. Include the version in the token. When the user logs out or changes their password, increment the version. Reject tokens with older versions.
- Short-lived tokens: If access tokens expire in 5 minutes, you only need to revoke the refresh token to effectively lock out the user within minutes.
6. HMAC vs RSA/ECDSA: Choose Wisely
The distinction matters for security architecture:
| Aspect | HMAC (HS256/384/512) | RSA/ECDSA (RS256, ES256) |
|---|---|---|
| Key Type | Single shared secret | Private key + public key |
| Who Can Sign | Anyone with the secret | Only private key holder |
| Who Can Verify | Anyone with the secret | Anyone with the public key |
| Best For | Single-server setups | Distributed / microservices |
| Performance | Faster signing and verification | Slower, but verification is still fast |
7. Rotate Keys Regularly
Key rotation limits the impact of a key compromise. Use the JWKS (JSON Web Key Set) pattern: publish your public keys at a well-known endpoint (e.g., /.well-known/jwks.json) and include a kid (Key ID) header in each token. When rotating keys, add the new key to the JWKS set and keep the old key available until all tokens signed with it have expired.
8. Minimize Payload Size
Include only what is necessary in the JWT payload. Every claim increases the token size, which adds overhead to every HTTP request (JWTs are typically sent in the Authorization header). More importantly, minimizing the payload reduces the information exposed if a token is leaked.
A good access token might contain: sub (user ID), iss, aud, exp, iat, and a roles or scope claim. Detailed user profile information should be fetched from an API, not embedded in the token.
Security Checklist
Use this checklist when implementing JWT-based authentication:
- Algorithm allowlist enforced in verification code
alg: noneexplicitly rejected- Signing key has sufficient entropy (256+ bits for HMAC)
- Access token lifetime is 15 minutes or less
- All standard claims (
exp,iss,aud) validated - Tokens stored in HTTP-only cookies (or short-lived if in localStorage)
- Refresh token rotation implemented
- Key rotation strategy in place
- Token revocation mechanism available for incident response
- Sensitive data is not stored in the payload
Audit Your Tokens
Use our JWT Decoder to paste any token and instantly see its security grade, algorithm analysis, and specific findings. The security tab flags common issues like weak algorithms, missing expiry claims, and long lifetimes -- all processed entirely in your browser.
Further Reading
- RFC 8725 — JWT Best Current Practices
IETF best practices for secure JWT implementation and deployment.
- OWASP JWT Cheat Sheet
OWASP guidance on common JWT vulnerabilities and mitigations.
- NIST SP 800-63B — Digital Identity Guidelines
NIST authentication and lifecycle management standards.