Guide · Developer security
JWT Security Best Practices
Everything you need to know about implementing JSON Web Tokens securely — algorithms, secret keys, common attacks, and safe storage.
Understanding JWT security
JSON Web Tokens (JWTs) are widely used for authentication and authorization, but they are also frequently misconfigured. This guide covers the essential security practices for working with JWTs.
Choosing the right algorithm
Symmetric algorithms (HMAC)
Use HMAC algorithms when the same party signs and verifies tokens.
HS256HMAC with SHA-256 (256-bit key minimum)HS384HMAC with SHA-384 (384-bit key minimum)HS512HMAC with SHA-512 (512-bit key minimum)Asymmetric algorithms (RSA / ECDSA)
Use asymmetric algorithms when different parties sign and verify tokens, such as across microservices.
RS256RSA with SHA-256 (2048-bit key minimum)ES256ECDSA with P-256 curve (recommended)ES384ECDSA with P-384 curveHS256 with a 256-bit secret, or ES256 for asymmetric signing. ES256 offers strong security with smaller keys and signatures than RSA.Secret key requirements
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 (development, staging, and production)
# Good: generate a secure 256-bit secret
openssl rand -base64 32
# Example output (DO NOT USE THIS!)
# K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols="secret", "jwt-secret", or "your-256-bit-secret".Need a key right now? Generate a JWT secret — created locally, never transmitted.
Common vulnerabilities & prevention
1Algorithm confusion (alg=none)
Attackers may try to change the algorithm to none or switch between symmetric and asymmetric algorithms.
// VULNERABLE: accepts any algorithm
jwt.verify(token, secret);
// SECURE: explicitly specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });2Missing 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);3Key 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'] });4Missing 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:
SignatureMust be valid for the specified algorithmexpToken must not be expirednbfToken must be active (not before)issMust match the expected issueraudMust include your application (audience)iatIssued-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
Access tokens15 minutes to 1 hourRefresh tokens7–30 days (stored securely)ID tokens5–15 minutesRefresh token strategy
- Use short-lived access tokens with longer-lived refresh tokens
- Store refresh tokens securely in HttpOnly cookies or secure storage
- Rotate refresh tokens by issuing a new one on every use
- Maintain a token blacklist or use token families for revocation
Storage best practices
In browser applications, where you keep a token determines how exposed it is. Options ranked by security:
1HttpOnly cookies — best protection against XSS2In-memory — good security, but lost on refresh3sessionStorage — tab-specific and cleared on close4localStorage — vulnerable to XSS (avoid)// 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:
- Short expiration — tokens naturally expire quickly
- Token blacklist — store revoked token IDs using the
jticlaim - Token versioning — increment the user's token version on logout
- Refresh token revocation — revoke refresh tokens to prevent new access tokens