Before you sign anything
Most JWT vulnerabilities trace back to five decisions: the key, the algorithm, the lifetime, the storage, and the revocation story. Run through this checklist before your auth code ships.
1. Key strength
- Generate the secret from a CSPRNG —
crypto.randomBytes(64)in Node,secrets.token_urlsafe(64)in Python,openssl rand -base64 64on the command line. Never a passphrase, company name, or doc example. - Match key size to algorithm: HS256 ≥ 32 bytes, HS384 ≥ 48 bytes, HS512 ≥ 64 bytes. A 16-character "secret" is brute-forceable in seconds.
- Never commit the secret to git. Store it in an environment variable or a secrets manager, and use different secrets for dev, staging, and production.
- Test your current key with a strength validator — if it fails entropy or length checks, rotate it.
2. Algorithm pinning
- Pin the expected algorithm server-side. Never read
algfrom the token header and follow it — that is the algorithm-confusion attack (alg: none, RS256→HS256 key confusion). - With libraries like
jsonwebtoken, passalgorithms: ["HS256"]explicitly on everyverify()call. - Reject tokens whose header algorithm doesn't match before computing anything.
3. Token lifetime
- Keep access tokens short: 5–15 minutes. A stolen long-lived JWT is valid until it expires — there is no server-side kill switch by default.
- Pair short access tokens with refresh tokens, and rotate the refresh token on every use.
- Always check
expandnbfserver-side. Compare in seconds, not milliseconds —Date.now()returns milliseconds and this mismatch silently breaks expiry checks.
4. Payload hygiene
- The payload is encoded, not encrypted — anyone holding the token can read it. Never put passwords, API keys, or sensitive PII in claims.
- Keep claims minimal: a user or session ID and the timestamps you need. Look up sensitive data server-side.
5. Storage
- Browser: prefer httpOnly, Secure, SameSite cookies over
localStorage— httpOnly keeps the token away from JavaScript and out of reach of XSS. - If you must use
localStorage(e.g. native-like SPAs without a backend cookie story), accept the XSS trade-off explicitly and invest in a strict CSP. - Never store refresh tokens in
localStorage.
6. Revocation and rotation
- Decide how a token dies before the token exists: a short expiry, a deny-list, or a version claim checked server-side.
- Plan key rotation from day one with a
kidheader so two secrets can validate during the rollover window. - Rotate immediately after any suspected leak, then invalidate issued tokens.
7. Verification hygiene
- Verify the signature on every request that trusts the token — decoding without verifying proves nothing.
- Reject tokens from cookies with the wrong
SameSite/origin context to limit CSRF exposure. - Log verification failures with their reason, but never log the full token or the secret.
Work through the list top to bottom — the first three items stop the most attacks. Generate a properly-sized key with the JWT secret generator, validate it, and you've already covered the items that cause the worst real-world incidents.