Security · 12 October 2026 · 3 min read

JWT Security: 7 Mistakes That Lead to Breaches

JSON Web Tokens are simple to use and easy to get wrong. The alg none trap, weak HMAC secrets, missing expiry, tokens in localStorage and other mistakes, with fixes.

JSON Web Tokens are everywhere: single sign-on, mobile apps, APIs between services. They are also behind a long list of security incidents, almost never because the standard is broken and almost always because of how it was used. These are the mistakes we see most in code reviews and penetration tests. To look inside a token while you read, paste it into the JWT decoder.

1. Treating the payload as secret

A JWT is signed, not encrypted. The header and payload are just Base64url, and anyone holding the token can read them. We regularly find email addresses, internal IDs, roles and sometimes even password hashes in tokens. Put only what the receiving service needs, and nothing you would mind seeing in a browser’s developer tools.

2. Accepting “alg: none” or letting the token choose

The JWT header names the signing algorithm. Early libraries trusted it, so an attacker could set alg to none, drop the signature and be accepted. A related attack switched RS256 to HS256 and used the public key as an HMAC secret. Modern libraries are safer, but the rule stands: the server decides which algorithm it expects and rejects anything else.

3. Weak HMAC secrets

With HS256, anyone who captures one token can try to guess the secret offline, at billions of guesses per second on a GPU, without touching your server. Secrets like “secret”, the app name or a short phrase from a tutorial fall in minutes. Use at least 32 random bytes, for example from the password generator at full length, and store it like any other key.

4. Not checking the claims

A valid signature only proves who issued the token. You also need to check that it has not expired (exp), is already valid (nbf), comes from the right issuer (iss) and was meant for your service (aud). Skipping the audience check is how a token issued for one internal app ends up working on another.

5. Tokens that live for weeks

JWTs are usually not stored on the server, which means they cannot simply be revoked. A stolen token with a 30-day lifetime is a 30-day pass. Keep access tokens short, 5 to 15 minutes, and use refresh tokens that the server can revoke for longer sessions.

6. Storing tokens where scripts can read them

Tokens in localStorage are readable by any JavaScript on the page, so a single cross-site scripting bug leaks every user’s session. For browser apps, an HttpOnly, Secure, SameSite cookie is usually the safer home, combined with CSRF protection where needed.

7. Logging and pasting tokens

Bearer tokens end up in access logs, error trackers, chat messages and support tickets. Each copy is a working credential until it expires. Mask the Authorization header in logs, and when you need to inspect a token, use a tool that does not send it anywhere. Our decoder runs entirely in your browser, but an expired or test token is still the better habit.

Our take

JWTs are a good fit for short-lived, stateless authorisation between services. They are a poor fit for classic web sessions, where a random session ID in a cookie and a server-side session store give you instant logout and revocation for free. Before choosing JWT, ask whether you actually need statelessness. If you do, use a well-maintained library, pin the algorithm, keep lifetimes short and verify every claim. Most breaches we have seen would have been prevented by those four lines of configuration.