JWT Security Checklist: 12 Things to Verify Before You Ship
JWT authentication has more failure modes than most developers realise. Correct signature verification is necessary but far from sufficient. This checklist is what I run through before every production JWT deployment.
The Checklist
Secret Is Generated With a CSPRNG
Not a password. Not a UUID. Not a timestamp. A cryptographically secure pseudorandom number generator output.
In Node.js:crypto.randomBytes(32).toString('hex')In Python:
secrets.token_hex(32)In the browser:
jwtsecretgenerator.com/tools/jwt-secret-generator
A 256-bit CSPRNG secret takes 10^59 years to brute force at current GPU speeds.Algorithm Is Explicitly Specified in
verify()// Wrong jwt.verify(token, secret); // Right jwt.verify(token, secret, { algorithms: ['HS256'] });expClaim Is Present and Validated
Short-lived tokens (15 minutes) limit the damage from leaks. Verify your library is actually checkingexp- some require explicit configuration.issandaudClaims Are Validated
Validates the token was issued by your service and intended for your API. Prevents token reuse across services.Tokens Are in
httpOnlyCookies, NotlocalStorage
localStorageis readable by any script on the page.httpOnlycookies are invisible to JavaScript.HTTPS Is Enforced
JWT in a query parameter over HTTP is visible in every proxy, CDN, and server log on the path. Use theAuthorization: Bearerheader over HTTPS only.Refresh Tokens Are Server-Side Revocable
Short access tokens + server-side refresh tokens = the ability to end sessions immediately. Long-lived access tokens without refresh logic cannot be revoked.The
jtiClaim Is Used If You Need Immediate Revocation
Store revokedjtivalues in Redis with TTL matching token expiry. Check on every request. Adds one Redis lookup per request - worth it for high-security endpoints.Different Secrets for Each Environment
Dev secret leaks should not compromise production. Keep them separate.Secret Is Not in Source Code or Version Control
Check:git log -S "JWT_SECRET" -- .- if any results appear, rotate immediately.Error Messages Do Not Reveal Why Verification Failed
// Bad - tells attacker whether to try a different token or different secret return res.status(401).json({ error: 'Token expired' }); // Good return res.status(401).json({ error: 'Unauthorized' });Payload Does Not Contain Sensitive Data
JWT payload is base64, not encrypted. Decode any JWT in 3 seconds atjwt.io. Never put passwords, full PII, or financial data in a JWT payload.
The full version of this checklist with code examples for each point is at jwtsecretgenerator.com/blog/jwt-security-checklist-2026.
Comments
No comments yet. Start the discussion.