JWT security: common mistakes and how to avoid them
JWTs are everywhere, and so are the mistakes. Learn what a JWT really guarantees and the checklist that keeps it safe.

A JSON Web Token (JWT) is a compact string with three parts: a header, a payload (claims such as user ID and expiry) and a signature. The server signs the token, and later checks the signature to trust the claims without a database lookup.
What a JWT does and does not give you
- It proves the token was signed by someone with the key, and not changed.
- It does not hide the content. The payload is only Base64 encoded, so anyone can read it. Never put secrets in it.
- It cannot be revoked by itself. Until it expires, it is valid, even if the user logs out or is banned.
Common mistakes
1. Trusting the algorithm in the header. Some libraries once accepted alg: none (no signature) or let the token choose the algorithm. Fix: the server fixes the allowed algorithms and rejects anything else.
2. Algorithm confusion. Verifying an RS256 token with the public key as if it were an HMAC secret. Fix: pin the algorithm, and use separate keys per purpose.
3. Weak or default signing secrets. A short or guessable HMAC secret can be brute-forced offline. Fix: use a long random secret (32+ bytes) from a secret manager, or use asymmetric keys.
4. No or very long expiry. A stolen token works forever. Fix: short-lived access tokens (for example 5 to 15 minutes) plus a refresh token that you can revoke.
5. Skipping claim checks. Always validate exp (expiry), iss (issuer), aud (audience) and nbf where used.
6. Storing tokens badly in the browser. In localStorage, any XSS can steal them. An HttpOnly, Secure, SameSite cookie keeps them out of JavaScript, but then protect against CSRF.
7. Trusting data in the token for authorization too long. Roles in the payload stay stale until expiry. Keep tokens short, or check critical permissions server-side.
8. Putting sensitive data in the payload. Remember it is readable.
Revocation options
- Short expiry plus refresh tokens stored server-side.
- A denylist of revoked token IDs (
jti) with expiry. - A token version in the user record: change it to invalidate all tokens.
Do you even need JWT?
For a normal web app with one backend, a server-side session (a random ID in an HttpOnly cookie) is simpler and easy to revoke. JWT shines for service-to-service calls and short-lived delegated access, not as a default for every login.
Checklist
- Allow-list algorithms and keys on the server.
- Strong secret or asymmetric keys, rotated.
- Short expiry, refresh with revocation.
- Validate
exp,iss,aud. - No secrets in the payload.
- HttpOnly cookie storage with CSRF protection, or a very short in-memory token.
Takeaway: a JWT proves who signed it, not that it is still valid. Pin algorithms, keep tokens short-lived and use sessions when you just need a login.
Web Security series, Part 2 (more bug classes and hardening): 1 of 6.
