Guide · Developer security
JWT Security Checklist
Essential security checklist for JWT implementation. Follow these proven practices to protect your applications from common JWT vulnerabilities and attacks.
Critical Security Priorities
1. Always Use HTTPSNever transmit JWTs over unencrypted connections2. Set Short ExpirationLimit token lifetime to reduce attack window3. Validate EverythingVerify signature, expiration, and all claimsSecurity Implementation Progress
Token Generation Security
Enforce HTTPS Onlycritical
Never generate, transmit, or accept JWT tokens over unencrypted HTTP connections.
# VULNERABLE: Never do this
http://api.example.com/login
# SECURE: Always use HTTPS
https://api.example.com/loginUse Cryptographically Strong Secretscritical
Generate secrets with at least 256 bits of entropy. Never use predictable or weak secrets.
// SECURE: Generate strong secret
const secret = crypto.randomBytes(32).toString('hex');
// Result: 64-character hex string (256 bits)Set Short Token Expirationhigh
Use 15-60 minutes for access tokens. Implement refresh tokens for longer sessions.
Access Tokens15-60 minutesAPI Tokens1-24 hoursRefresh Tokens7-30 daysValidate and Specify Algorithmcritical
Always specify and validate the expected signing algorithm. Prevent algorithm confusion attacks.
// VULNERABLE: Accepts any algorithm
jwt.verify(token, secret)
// SECURE: Specify expected algorithm
jwt.verify(token, secret, { algorithms: ['HS256'] })Never Store Sensitive Data in JWTcritical
JWTs are encoded, not encrypted. Never include passwords, SSNs, or other sensitive information.
Never Include
- Passwords or password hashes
- Social Security Numbers
- Credit card information
- API secrets or keys
- Personal identification numbers
Safe to Include
- User ID (non-sequential)
- Username or email
- User roles and permissions
- Non-sensitive metadata
- Application preferences
Token Validation Security
Always Verify Token Signaturecritical
Never skip signature verification. This is your primary defense against token tampering.
// SECURE: Proper verification
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
// Token is valid and verified
} catch (error) {
// Invalid token - reject request
return res.status(401).json({ error: 'Invalid token' });
}Validate Token Expirationcritical
Always check the 'exp' claim. Expired tokens should be immediately rejected.
// Expiration is automatically checked by most libraries
// But you can also manually verify:
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
throw new Error('Token has expired');
}Validate Token Issuer (iss)high
Check that tokens come from expected issuers to prevent token confusion attacks.
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'your-app-name',
audience: 'your-api'
});Validate Token Audience (aud)high
Ensure tokens are intended for your application by validating the audience claim.
// Token should specify intended audience
const payload = {
sub: 'user123',
aud: 'api.yourapp.com', // Your API endpoint
iss: 'yourapp.com'
};Validate 'Not Before' Claim (nbf)medium
If using nbf claim, ensure tokens are not used before their valid time.
if (decoded.nbf && Date.now() < decoded.nbf * 1000) {
throw new Error('Token not yet valid');
}Storage and Transmission Security
Use Secure Storage Methodshigh
Store tokens in httpOnly cookies or secure storage. Avoid localStorage for sensitive tokens.
Secure Options
- httpOnly cookies
- Secure cookies
- SameSite cookies
- Memory (temporary)
Use with Caution
- localStorage (XSS risk)
- sessionStorage
- Regular cookies
Never Use
- URL parameters
- Referer headers
- Unencrypted storage
- Plain text files
Use Authorization Headerhigh
Send tokens in Authorization header with Bearer scheme, not in URL or body.
# SECURE: Correct way
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# VULNERABLE: Never in URL
https://api.example.com/data?token=eyJhbGc...Implement CSRF Protectionhigh
Use CSRF tokens or double-submit cookie pattern when storing JWTs in cookies.
// Set secure cookie with CSRF protection
res.cookie('jwt', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});Configure CORS Properlymedium
Set specific origins in CORS, never use wildcards (*) for credentials.
# SECURE: Specific origins
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Credentials: true
# VULNERABLE: Never with credentials
Access-Control-Allow-Origin: *Application Security
Implement Refresh Token Strategyhigh
Use refresh tokens for long-lived sessions to minimize exposure of access tokens.
// Access token: short-lived (15-60 minutes)
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
// Refresh token: longer-lived (7-30 days)
const refreshToken = jwt.sign({ type: 'refresh' }, secret, { expiresIn: '7d' });Implement Token Blacklistinghigh
Maintain a blacklist for revoked tokens, especially for logout and security incidents.
// Store revoked token IDs in Redis/database
await redis.setex('blacklist:' + jti, expTime, 'true');
// Check blacklist on each request
const isBlacklisted = await redis.get('blacklist:' + decoded.jti);Implement Rate Limitingmedium
Add rate limiting to token endpoints to prevent brute force and abuse.
Login Endpoint
- 5 attempts per minute per IP
- 10 attempts per hour per user
- Progressive delays
Token Refresh
- 10 requests per minute
- Monitor for unusual patterns
- Implement circuit breakers
Implement Security Loggingmedium
Log security events for monitoring and incident response.
// Log security events
logger.warn('Invalid JWT signature', { ip, userAgent, token: token.slice(0,20) });
logger.info('Token refreshed', { userId, ip });
logger.error('Expired token used', { userId, expired: decoded.exp });Set Security Headersmedium
Configure security headers to protect against common attacks.
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'Development and Testing
Secure Environment Configurationcritical
Use environment variables for secrets, never hardcode in source code.
// VULNERABLE: Never do this
const secret = 'mysecretkey123';
const secret = 'hardcoded-in-repo';
// SECURE: Always do this
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('Missing JWT_SECRET');Use Different Secrets Per Environmenthigh
Development, staging, and production should use completely different secrets.
# Development .env
JWT_SECRET=dev_secret_32_char_minimum_12345
# Production .env
JWT_SECRET=prod_different_secret_67890_xyz
# Never reuse secrets across environmentsTest Security Scenariosmedium
Write tests for token validation, expiration, and attack scenarios.
// Test cases to include:
// - Expired token rejection
// - Invalid signature detection
// - Malformed token handling
// - Algorithm confusion prevention
// - Missing claims validationRegular Security Auditsmedium
Conduct regular security reviews and dependency updates.
Monthly Tasks
- Review token lifetimes
- Check for library updates
- Analyze security logs
- Review access patterns
Quarterly Tasks
- Rotate signing secrets
- Security penetration testing
- Update security policies
- Team security training
Document Security Decisionsmedium
Document your JWT configuration, security policies, and incident procedures.
- Document algorithm choices and rationale
- Record token lifetime decisions
- Maintain incident response procedures
- Document key rotation schedule
- Keep security contact information current