JWT Security Checklist: 12 Things to Verify Before You Ship
DEV Community

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

  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'] });
    
  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' });
    
  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.

Comments

No comments yet. Start the discussion.