JWT Security Checklist 2026 — Production Authentication Guide
JWT authentication is deceptively simple to implement and notoriously difficult to secure. A single misconfiguration — trusting the alg header, skipping expiration checks, or storing tokens in localStorage — can compromise your entire user base.
This 2026 production checklist covers every layer of JWT security, from secret generation through token storage and revocation. Use it before every production deployment.
1. Key Management
Your signing key is the root of trust. Compromise it and every token — past and future — is suspect.
- [ ] Generate secrets with a CSPRNG (
crypto.randomBytes,crypto.getRandomValues) - [ ] Use minimum 256-bit keys for HS256; 2048-bit RSA for RS256
- [ ] Store secrets in environment variables or a secrets manager — never in source code
- [ ] Use different secrets for development, staging, and production
- [ ] Implement key rotation with the
kidheader claim - [ ] Never log, echo, or expose secrets in error messages
Generate production keys with the JWT Secret Generator and verify integration with the JWT Validator.
2. Algorithm Hardening
Algorithm confusion attacks remain one of the most exploited JWT vulnerabilities.
- [ ] Always specify the expected algorithm in
verify()— never trust the token'salgheader - [ ] Reject tokens with
alg: none - [ ] Disable deprecated algorithms (HS1, RS1) if your library supports them
- [ ] Use RS256 when multiple services verify tokens without sharing secrets
- [ ] Document your algorithm choice and enforce it in middleware
// CORRECT — algorithm is pinned server-side
jwt.verify(token, secret, { algorithms: ['HS256'] });
// WRONG — trusts whatever alg the attacker sets
jwt.verify(token, secret);3. Claim Validation
Verified signatures are necessary but not sufficient. Validate every claim your application depends on.
| Claim | Validation Rule |
|---|---|
| `exp` | Reject if current time >= expiration |
| `nbf` | Reject if current time < not-before |
| `iat` | Reject if issued in the future (clock skew tolerance: 30s) |
| `iss` | Must match your expected issuer URL |
| `aud` | Must match your API identifier |
| `sub` | Must be a valid user ID in your system |
The exp claim is the most commonly skipped validation. Libraries may verify signatures but leave expiration checking to you depending on configuration.
jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://auth.myapp.com',
audience: 'https://api.myapp.com',
clockTolerance: 30,
});4. Token Lifetime and Refresh Strategy
Short-lived access tokens limit the damage from leakage.
- [ ] Set access token lifetime to 15 minutes or less
- [ ] Use refresh tokens for session continuity (stored server-side or in httpOnly cookies)
- [ ] Rotate refresh tokens on each use
- [ ] Invalidate refresh tokens on logout and password change
- [ ] Never put sensitive data in JWT payloads — tokens are readable by anyone
| Token Type | Recommended Lifetime | Storage |
|---|---|---|
| Access token | 5–15 minutes | Memory or httpOnly cookie |
| Refresh token | 7–30 days | httpOnly cookie or server DB |
| ID token (OIDC) | 5–15 minutes | Memory |
5. Token Storage (Client-Side)
Where you store tokens on the client determines your XSS vulnerability surface.
| Storage | XSS Risk | CSRF Risk | Recommendation |
|---|---|---|---|
| localStorage | High | None | Avoid for auth tokens |
| sessionStorage | High | None | Avoid for auth tokens |
| httpOnly cookie | None | Medium | Preferred for web apps |
| Memory (JS variable) | Medium | None | Acceptable for SPAs |
Read the full localStorage vs cookies comparison for architecture guidance. The short version: httpOnly cookies with SameSite=Strict and CSRF protection beat localStorage for most web applications.
6. Transport Security
- [ ] Enforce HTTPS on all endpoints that issue or accept tokens
- [ ] Set
Secureflag on auth cookies - [ ] Use
Authorization: Bearerheader, not query parameters - [ ] Never include tokens in URLs — they end up in server logs and browser history
- [ ] Implement HSTS with a minimum max-age of one year
7. Payload Design
Keep JWT payloads minimal. Every byte in the token travels with every request.
Include:
sub— user identifierexp,iat— timing claimsjti— unique token ID for revocation tracking
Exclude:
- Passwords or password hashes
- Credit card numbers or PII beyond user ID
- Permissions that change frequently (use server-side lookups instead)
8. Revocation and Denylists
JWTs are stateless by design, which makes immediate revocation challenging.
Strategies ranked by effectiveness:
1. Short access token lifetime — limits exposure window without infrastructure
2. Refresh token revocation — delete refresh token from DB on logout
3. Token denylist — store revoked jti values in Redis with TTL matching token expiry
4. Version claim — increment a tokenVersion on the user record; reject stale versions
For high-security applications, combine short lifetimes with a Redis denylist keyed by jti.
9. Monitoring and Incident Response
- [ ] Log authentication failures with rate limiting on log volume
- [ ] Alert on spikes in 401 responses (possible token forgery attempts)
- [ ] Monitor for tokens with unexpected
algvalues in request logs - [ ] Have a secret rotation runbook ready before you need it
- [ ] Test rotation in staging quarterly
10. Pre-Deployment Verification
Before shipping to production, verify every item:
# Test with the JWT Validator during QA
# 1. Valid token → 200
# 2. Expired token → 401
# 3. Wrong secret → 401
# 4. alg:none token → 401
# 5. Missing Authorization header → 401Use the JWT Validator to generate test cases for each scenario.
Quick Reference: Red Flags
If any of these are true in your codebase, fix them before production:
jwt.decode()used in authorization middleware- Secret hardcoded in source files
- Tokens stored in localStorage without XSS mitigations
- No
expvalidation - Algorithm not explicitly specified in
verify() - Token passed in URL query strings
- Refresh tokens without rotation