JWT Security Best Practices
Everything you need to know about implementing JWTs securely.
Understanding JWT Security
JSON Web Tokens (JWTs) are widely used for authentication and authorization, but they're also frequently misconfigured. This guide covers the essential security practices for working with JWTs.
Important: JWTs are signed, not encrypted. Anyone can read the payload - the signature only ensures it hasn't been tampered with. Never put sensitive data in a JWT without additional encryption.
Choosing the Right Algorithm
Symmetric Algorithms (HMAC)
Use HMAC algorithms when the same party signs and verifies tokens.
HS256- HMAC with SHA-256 (256-bit key minimum)HS384- HMAC with SHA-384 (384-bit key minimum)HS512- HMAC with SHA-512 (512-bit key minimum)
Asymmetric Algorithms (RSA/ECDSA)
Use asymmetric algorithms when different parties sign and verify tokens (e.g., microservices).
RS256- RSA with SHA-256 (2048-bit key minimum)RS384- RSA with SHA-384RS512- RSA with SHA-512ES256- ECDSA with P-256 curve (recommended)ES384- ECDSA with P-384 curveES512- ECDSA with P-521 curve
Recommendation: For most applications, use HS256 with a 256-bit secret or ES256 for asymmetric signing. ES256 offers better performance than RS256 with equivalent security.
Secret Key Requirements
HMAC Secret Keys
For HMAC algorithms, your secret key should be:
- At least as long as the hash output (256 bits for HS256)
- Generated using a cryptographically secure random number generator
- Unique per environment (dev/staging/production)
# Good: Generate a secure 256-bit secret
openssl rand -base64 32
# Example output (DO NOT USE THIS!)
# K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=Never use: Short secrets, dictionary words, application names, or predictable values like "secret", "jwt-secret", or "your-256-bit-secret".
Common Vulnerabilities & Prevention
1. Algorithm Confusion (alg=none)
Attackers may try to change the algorithm to "none" or switch between symmetric/asymmetric algorithms.
// VULNERABLE: Accepts any algorithm
jwt.verify(token, secret);
// SECURE: Explicitly specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });2. Missing Signature Verification
Always verify the signature before trusting token contents.
// VULNERABLE: Decodes without verification
const payload = jwt.decode(token);
// SECURE: Verifies signature first
const payload = jwt.verify(token, secret);3. Key Confusion Attack
When using RS256, attackers may try to verify with the public key as an HMAC secret.
// VULNERABLE: Could be tricked into HMAC verification
jwt.verify(token, publicKey);
// SECURE: Explicitly require RS256
jwt.verify(token, publicKey, { algorithms: ['RS256'] });4. Missing Expiration
Tokens without expiration never become invalid:
// Always include expiration
const token = jwt.sign(
{ userId: 123 },
secret,
{ expiresIn: '1h' } // or use 'exp' claim directly
);Token Validation Checklist
Always validate these claims when verifying a JWT:
- Signature - Must be valid for the specified algorithm
- exp (expiration) - Token must not be expired
- nbf (not before) - Token must be active
- iss (issuer) - Must match expected issuer
- aud (audience) - Must include your application
- iat (issued at) - Should not be in the future
// Comprehensive validation
jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://yourapp.com',
audience: 'your-api',
clockTolerance: 30, // 30 second clock skew
});Token Lifetime & Refresh
Recommended Lifetimes
- Access tokens: 15 minutes to 1 hour
- Refresh tokens: 7-30 days (stored securely)
- ID tokens: 5-15 minutes
Refresh Token Strategy
Use short-lived access tokens with longer-lived refresh tokens:
- Store refresh tokens securely (HttpOnly cookies or secure storage)
- Implement refresh token rotation (issue new refresh token on each use)
- Maintain a token blacklist or use token families for revocation
Storage Best Practices
Browser Applications
Options ranked by security:
- HttpOnly Cookies - Best protection against XSS
- In-memory - Good security, lost on refresh
- sessionStorage - Tab-specific, cleared on close
- localStorage - Vulnerable to XSS (avoid)
Cookie Configuration
// Secure cookie settings
res.cookie('token', jwt, {
httpOnly: true, // Prevents JavaScript access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour
path: '/',
});Token Revocation
JWTs are stateless by design, making revocation challenging. Consider these approaches:
Strategies
- Short expiration - Tokens naturally expire quickly
- Token blacklist - Store revoked token IDs (jti claim)
- Token versioning - Increment user's token version on logout
- Refresh token revocation - Revoke refresh tokens to prevent new access tokens
Pro tip: For high-security applications, consider using opaque tokens (random strings) that reference server-side sessions instead of JWTs. This provides instant revocation at the cost of a database lookup per request.
Implementation Checklist
- [ ] Use a well-maintained JWT library
- [ ] Generate secrets with CSPRNG (at least 256 bits)
- [ ] Explicitly specify allowed algorithms
- [ ] Always verify signatures before trusting payloads
- [ ] Set appropriate expiration times
- [ ] Validate issuer and audience claims
- [ ] Use HttpOnly cookies for browser storage
- [ ] Implement refresh token rotation
- [ ] Have a token revocation strategy
- [ ] Never store sensitive data in JWT payloads