JWT mistakes that keep showing up in code review
A JSON Web Token is three base64url pieces joined by dots. Most of the problems come from treating that structure as more than it is.
The payload is not secret
The header and payload are only encoded. Anyone holding the token can read them without a key. We built a token with a role claim in it and decoded the middle part with nothing but a base64 call: the claims came out in plain JSON. The signature proves the content was not changed, not that it is hidden. Do not put personal data, internal identifiers you would not show a user, or anything sensitive in a JWT that leaves your servers.
Verification mistakes
- Trusting the header's
alg. Some libraries once acceptedalg: noneand skipped verification. Others allowed algorithm confusion: a token signed with HS256 using the server's RSA public key as the secret was accepted by code that expected RS256. The rule is to decide the algorithm on the server and pass it explicitly, for examplejwt.decode(token, key, algorithms=["RS256"])in PyJWT, and never derive it from the token. - Decoding without verifying. Many libraries have a decode function that skips the signature check for debugging. Search the codebase for it.
- Not validating claims. A valid signature on an expired token is still a bad token. Check
exp,nbf, the issuer (iss) and the audience (aud), so a token minted for one service is not accepted by another. Allow a small clock skew and no more. - Key confusion through
kid. If thekidheader selects a key from a file or a database, treat it as untrusted input. Look it up in a fixed allowlist.

Storage and revocation
Where the browser keeps the token decides which attack you are exposed to. In localStorage, any script injected through XSS can read it. In an HttpOnly cookie, scripts cannot read it, but the browser sends it automatically, so you need CSRF protection and a suitable SameSite value.
A signed token stays valid until it expires, and there is no server-side state to delete. That makes logout, password change and account suspension awkward. The usual compromise is a short-lived access token (minutes) with a longer-lived refresh token that the server stores, can revoke, and rotates on each use.
Is a JWT even the right thing?
If one backend serves one web front end, a server-side session with an opaque ID in a cookie is simpler and can be revoked instantly. JWTs earn their place when several independent services must verify a caller without a shared session store. Choosing them by default adds the failure modes above for a benefit you may not need.
What alg: none actually looks like
The historical alg: none bug is easy to underestimate until you see the token. It is a completely ordinary-looking JWT with an empty third segment:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJzdWIiOiAidXNlci0xIiwgInJvbGUiOiAiYWRtaW4ifQ.Decoding the first two segments with nothing more than base64 gives {"alg": "none", "typ": "JWT"} and {"sub": "user-1", "role": "admin"}. A verifier that reads alg from the header and dispatches on it, instead of insisting on an algorithm the caller expects, will treat the trailing dot and empty signature as "verified". Modern libraries reject none by default, but the underlying lesson generalises beyond that one historical bug: never let the token tell you how to check itself.
Key rotation without downtime
Services that verify tokens from an external identity provider usually fetch public keys from a JWKS (JSON Web Key Set) endpoint rather than hardcoding one key, exactly so the provider can rotate keys without breaking every verifier at once. The kid header picks which key from the set to use, which is why it has to be checked against a known set rather than trusted as a file path or lookup key on its own, as the article above already covers. Cache the JWKS response respecting its Cache-Control header, but also handle a kid that is not in your cached copy by refetching once before rejecting the token outright, since a legitimate token signed with a freshly rotated key would otherwise be rejected during the rotation window.