JWT Security Best Practices
JWTs are powerful but come with security considerations. Follow these practices to keep your implementation secure.
1. Use Strong Secrets
// BAD: Weak secret
const secret = "password123";
// GOOD: Strong, random secret (256 bits minimum)
const secret = crypto.randomBytes(32).toString('hex');
2. Always Validate Tokens
// Verify signature, expiration, and issuer
const jwt = require('jsonwebtoken');
try {
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // Specify allowed algorithms
issuer: 'your-app',
audience: 'your-api',
});
} catch (err) {
// Token invalid - reject request
}
3. Set Short Expiration Times
// Access tokens: 15 minutes
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
// Use refresh tokens for longer sessions
const refreshToken = jwt.sign({ userId }, refreshSecret, { expiresIn: '7d' });
4. Never Store Sensitive Data in Payload
The payload is only Base64 encoded, not encrypted:
// BAD: Sensitive data exposed
{
"userId": 123,
"password": "secret", // NEVER do this
"creditCard": "4111..." // NEVER do this
}
// GOOD: Only store references
{
"userId": 123,
"role": "admin"
}
5. Protect Against Algorithm Confusion
// Always specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });
// Reject tokens with "alg": "none"
6. Secure Token Storage (Frontend)
// For web apps: HttpOnly cookies (preferred)
// Protects against XSS attacks
// If using localStorage (less secure):
// - Never store refresh tokens
// - Implement CSRF protection
7. Implement Token Revocation
Since JWTs are stateless, implement a blacklist for revoked tokens:
const revokedTokens = new Set();
function isRevoked(token) {
const { jti } = jwt.decode(token);
return revokedTokens.has(jti);
}