Common JWT Security Mistakes

Avoid alg none, weak HMAC secrets, long-lived tokens, and confusing decode with verify.

By Mariana Soto · Published 2026-07-05 · Updated 2026-08-22

Most JWT incidents in production come from predictable mistakes — not exotic cryptography breaks. Security reviews and incident postmortems repeatedly surface the same patterns. This article catalogs the failures we see most often and how to avoid them, with references to tools on Cyberway for safe learning.

Accepting alg none

Some libraries historically allowed "alg": "none" — no signature. If your verifier trusts the header's alg field without an allowlist, an attacker sets alg to none, removes the signature, and forges any payload.

Fix: Pin allowed algorithms in verification config:

await jwtVerify(token, key, { algorithms: ["HS256"] }); // never include "none"

Never branch verification logic based solely on header.alg from the token. RFC 8725 (JWT Best Current Practices) explicitly warns against accepting none and related algorithm confusion attacks.

Weak HMAC secrets

Secrets like secret, password, or short strings are offline brute-forceable. Once recovered, an attacker mints valid tokens for any user.

Fix: Use at least 256 bits of cryptographically random secret material. Rotate on compromise. Prefer RS256/ES256 when many services verify — only the auth issuer holds the private key.

Cyberway's JWT Generator and verify panel warn about weak HMAC secrets during local testing. Treat that as a teaching signal, not a substitute for secret management in production (use a vault, not .env in git).

Treating decode as authenticate

Anyone can decode a JWT. Base64URL is not encryption. A dashboard that displays role: admin from a decoded token without verification is a vulnerability waiting for an attacker-crafted payload.

Fix: Separate "inspect for debugging" from "trust for authorization." Only verified claims from jwtVerify (or equivalent) should gate access.

Attack flow:

  1. Attacker decodes a legitimate token, reads claim structure.
  2. Attacker crafts new payload { "sub": "victim", "role": "admin" }.
  3. Without verification, the API accepts the forged token.

Paste suspicious tokens into the Cyberway JWT Decoder to inspect structure, then always verify server-side.

Eternal tokens

Tokens without exp, or with multi-year lifetimes, are hard to revoke. Stolen tokens remain valid until manual key rotation — which invalidates all sessions.

Fix: Short access tokens (5–15 minutes) plus refresh tokens stored and revocable server-side. Include exp on every access token.

Storing JWTs in localStorage

SPAs often store access tokens in localStorage for convenience. Any XSS vulnerability on your domain lets injected JavaScript read and exfiltrate tokens.

Fix: Prefer HttpOnly cookies for browser sessions, or keep access tokens in memory only. Invest in XSS prevention (CSP, sanitization) regardless of storage choice.

See JWT vs session cookies for a fuller comparison.

Skipping aud and iss checks

A token issued for https://api-a.example.com might be cryptographically valid but intended for a different audience. Without aud validation, api-b accepts it — confused deputy problem.

Similarly, accepting any iss allows tokens from a compromised or test issuer.

Fix:

await jwtVerify(token, key, {
  algorithms: ["RS256"],
  issuer: "https://auth.example.com",
  audience: "https://api.example.com",
});

Logging full tokens

Developers log Authorization headers during debugging and forget to remove it. Logs aggregate to third-party services. JWTs in logs are replayable credentials until expiry.

Fix: Log sub, jti, or a hash prefix — never the full token. Redact Authorization in log middleware by default.

Trusting the wrong key

Teams occasionally verify tokens with an outdated secret after rotation, or fetch JWKS once at startup and never refresh. Tokens signed with the new key fail with "invalid signature" even though clients did nothing wrong.

Fix: Refresh JWKS on kid mismatch. Automate key rotation drills quarterly.

Copying tokens between environments

A developer copies a production JWT into a staging Cyberway decoder tab "just to debug." Browser extensions, screen shares, and support tickets spread credentials.

Fix: Use environment-specific issuers and secrets so production tokens fail fast in staging verifiers. Never paste production tokens into third-party tools — Cyberway stays client-side, but the token still appears on screen.

Ignoring token binding

Mobile apps and SPAs sometimes treat any valid JWT as proof of user presence without binding to device fingerprint, IP range, or refresh rotation. Stolen tokens work until expiry.

Fix: Short lifetimes, refresh rotation (one-time use refresh tokens), and anomaly detection on auth events. Defense in depth beats any single JWT claim.

Checklist before shipping auth changes

Before merging JWT-related changes to production, confirm: algorithms are allowlisted, exp is enforced, secrets live in a vault, logs redact bearer tokens, and staging uses distinct issuer URLs. Run through one token in the Cyberway decoder and one in your integration tests — they should agree on claims.

Next steps

Practice safely with the JWT Decoder and JWT Generator, then read the Complete JWT Guide. Try the weekly challenge to spot deliberate flaws in public samples.

Reference: OWASP JWT Cheat Sheet for language-specific guidance, and RFC 7519 for registered claim definitions including mandatory exp handling in compliant verifiers.

Why these mistakes persist

JWT libraries make decoding trivial — one function call — while secure verification requires configuration (algorithms, issuer, audience, clock skew). Teams copy decode snippets from tutorials and ship them to production. The Cyberway decoder is designed for the decode step only; pairing it with server-side jwtVerify in your codebase closes the gap between inspection and trust.

Try the Cyberway JWT Decoder or JWT Generator — both run entirely in your browser.