DEV Community

Cover image for JWT Security Checklist: 12 Things to Verify Before You Ship
SHAHJAHAN MD. SWAJAN
SHAHJAHAN MD. SWAJAN

Posted on

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.

1. 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.

2. Algorithm Is Explicitly Specified in verify()

// Wrong
jwt.verify(token, secret);

// Right
jwt.verify(token, secret, { algorithms: ['HS256'] });
Enter fullscreen mode Exit fullscreen mode

3. exp Claim Is Present and Validated

Short-lived tokens (15 minutes) limit the damage from leaks. Verify your library is actually checking exp — some require explicit configuration.

4. iss and aud Claims Are Validated

Validates the token was issued by your service and intended for your API. Prevents token reuse across services.

5. Tokens Are in httpOnly Cookies, Not localStorage

localStorage is readable by any script on the page. httpOnly cookies are invisible to JavaScript.

6. HTTPS Is Enforced

JWT in a query parameter over HTTP is visible in every proxy, CDN, and server log on the path. Use the Authorization: Bearer header over HTTPS only.

7. 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.

8. The jti Claim Is Used If You Need Immediate Revocation

Store revoked jti values in Redis with TTL matching token expiry. Check on every request. Adds one Redis lookup per request — worth it for high-security endpoints.

9. Different Secrets for Each Environment

Dev secret leaks should not compromise production. Keep them separate.

10. Secret Is Not in Source Code or Version Control

Check: git log -S "JWT_SECRET" -- . — if any results appear, rotate immediately.

11. 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' });
Enter fullscreen mode Exit fullscreen mode

12. Payload Does Not Contain Sensitive Data

JWT payload is base64, not encrypted. Decode any JWT in 3 seconds at jwt.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.

Top comments (1)

Collapse
 
circuit profile image
Rahul S

Solid list. Two things I'd add that sit inside items you already have. On #2 — "specify allowed algorithms" isn't quite enough if the allowlist mixes classes. The moment {RS256, HS256} are both accepted, you've re-opened the classic confusion attack: the attacker flips the header to HS256 and signs the token with your RSA public key as the HMAC secret. Public key is, well, public. Pin to a single algorithm the verifier enforces, and never let the token's own header choose.

The other one is a tension between #3 and #8 rather than a missing item. A 15-minute exp plus a Redis jti denylist for "immediate revocation" means you're doing a datastore lookup on every request — which is exactly the statefulness JWTs were picked to avoid. That's fine, but then the access token being stateless isn't buying you anything; you're paying the round-trip anyway. The honest fork is pick one: keep it truly stateless and accept up-to-15-min staleness on revoke (revoke only at the refresh boundary), or go stateful on purpose. Doing a per-request lookup and keeping short-lived tokens is the worst of both — you pay the lookup and still carry the blind window.