Understanding JWT Security Risks
JSON Web Tokens (JWTs) are widely used for authentication, but they come with security considerations that developers must understand. Unlike session-based authentication, JWTs are stateless and self-contained, which means the security model is different.
Critical JWT Security Best Practices
1. Use Strong Signing Algorithms
Always use RS256 (RSA with SHA-256) or ES256 (ECDSA with SHA-256) instead of HS256 for production applications:
// Good: Use RS256 for asymmetric signing
{
"alg": "RS256",
"typ": "JWT"
}
// Avoid: HS256 is vulnerable to key compromise
{
"alg": "HS256",
"typ": "JWT"
}
2. Implement Proper Token Validation
Always validate tokens on the server side, even if they appear valid:
// Node.js example with jsonwebtoken
const jwt = require('jsonwebtoken');
function validateToken(token) {
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'your-app',
audience: 'your-users'
});
return decoded;
} catch (error) {
throw new Error('Invalid token');
}
}
3. Set Appropriate Expiration Times
Use short-lived access tokens (15-30 minutes) and implement refresh tokens for longer sessions:
// Access token with short expiration
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
privateKey,
{
algorithm: 'RS256',
expiresIn: '15m',
issuer: 'your-app',
audience: 'your-users'
}
);
// Refresh token with longer expiration
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
refreshSecret,
{ expiresIn: '7d' }
);
4. Secure Token Storage
Never store JWTs in localStorage for sensitive applications. Use httpOnly cookies or secure storage:
// Secure cookie storage
res.cookie('accessToken', accessToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 15 * 60 * 1000 // 15 minutes
});
// For mobile apps, use secure storage
// React Native: AsyncStorage with encryption
// Flutter: flutter_secure_storage
// iOS: Keychain Services
// Android: Android Keystore
5. Implement Token Rotation
Rotate refresh tokens on each use to prevent token replay attacks:
async function refreshAccessToken(refreshToken) {
// Validate refresh token
const decoded = jwt.verify(refreshToken, refreshSecret);
// Generate new tokens
const newAccessToken = generateAccessToken(decoded.userId);
const newRefreshToken = generateRefreshToken(decoded.userId);
// Invalidate old refresh token
await invalidateToken(refreshToken);
return { newAccessToken, newRefreshToken };
}
Common JWT Vulnerabilities to Avoid
Algorithm Confusion Attacks
Always specify the expected algorithm when verifying tokens:
// Vulnerable - accepts any algorithm
jwt.verify(token, secret);
// Secure - specifies expected algorithm
jwt.verify(token, secret, { algorithms: ['RS256'] });
None Algorithm Attacks
Never allow the "none" algorithm in production:
// This header would bypass signature verification
{
"alg": "none",
"typ": "JWT"
}
Key Confusion Attacks
Use different keys for signing and verification in asymmetric algorithms:
// Use separate keys for signing and verification
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');
// Sign with private key
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
// Verify with public key
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
Additional Security Measures
Token Blacklisting
Implement token blacklisting for immediate revocation:
// Redis-based token blacklist
const redis = require('redis');
const client = redis.createClient();
async function blacklistToken(token) {
const decoded = jwt.decode(token);
const exp = decoded.exp;
const ttl = exp - Math.floor(Date.now() / 1000);
if (ttl > 0) {
await client.setex(`blacklist:${token}`, ttl, '1');
}
}
async function isTokenBlacklisted(token) {
const result = await client.get(`blacklist:${token}`);
return result === '1';
}
Rate Limiting
Implement rate limiting for token generation and validation endpoints:
// Express.js rate limiting example
const rateLimit = require('express-rate-limit');
const tokenLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many token requests, please try again later'
});
app.use('/api/auth/token', tokenLimiter);
Monitoring and Logging
Implement comprehensive logging for security monitoring:
- Log all token generation and validation attempts
- Monitor for suspicious patterns (rapid token generation, unusual IPs)
- Alert on failed validation attempts
- Track token usage patterns
Test Your JWT Security
Use our free JWT verification tool to test and validate your tokens securely.
Use JWT Verifier Tool