Reference

Security threats & mitigations

JWTs have a long history of subtle implementation bugs. These are the attacks that actually show up in incident reports, with the defenses that stop them.

The classics

Attacks you must defend against

The good news: every one of these has a one-line fix. The bad news: a hand-rolled verifier almost certainly misses at least three.

alg: none acceptance

Critical

The spec defines an "unsecured" mode where alg is literally "none" and the signature segment is empty. Verifiers that read alg from the token header will happily accept any payload as valid.

Mitigation. Configure your verifier with an allow-list of acceptable algorithms — never derive it from the token. Most modern libraries refuse none by default; make sure yours does.

Algorithm confusion (RS256 → HS256)

Critical

If your verifier accepts both RSA and HMAC, an attacker can take a token, change alg from RS256 to HS256, and sign it using your public key as the HMAC secret. Naive implementations treat the public key bytes as a shared secret and validate the forged token.

Mitigation. Pin the expected algorithm for each issuer; reject anything else. Pass keys to your verifier as a typed object (RSA key, EC key) — not raw bytes that could be reinterpreted.

Weak or guessable HS256 secrets

High

An HMAC signature can be brute-forced offline by anyone who captures a single token. Short, dictionary, or low-entropy secrets ("secret", "changeme", app names) fall in seconds on a laptop.

Mitigation. Generate ≥ 256-bit secrets from a CSPRNG (openssl rand -base64 64). Store in a secrets manager. Better still: switch to RS256 or ES256 so there's no shared secret to leak in the first place.

Token theft via XSS

High

If you store a JWT in localStorage, any successful XSS — a third-party script, a markdown renderer bug — can read and exfiltrate it. The token grants the attacker full access for its entire TTL.

Mitigation. Store tokens in an HttpOnly; Secure; SameSite=Lax cookie so JavaScript can't read them. Pair with a strict Content-Security-Policy and Subresource Integrity for any third-party scripts you do load.

CSRF on cookie-stored tokens

Medium

Cookies fix XSS theft but open the door to cross-site request forgery: a malicious page can trigger a request to your API and the browser will attach the cookie automatically.

Mitigation. SameSite=Lax covers most cases. For state-changing endpoints add a CSRF token (double-submit cookie or synchronizer pattern). Always require a custom header on AJAX so simple cross-origin POSTs can't forge requests.

Replay & the revocation gap

High

A stolen JWT remains valid until exp — there is no built-in "logout". Long-lived tokens turn a brief compromise into an extended foothold.

Mitigation. Short TTLs (5–15 min) plus refresh tokens with server-side rotation. Maintain a denylist of jtis for force-revocation. On material events (password change, suspicious login) rotate the signing key.

kid injection (SQLi, path traversal)

Medium

The kid header is attacker-controlled. If your verifier feeds it directly into a SQL query or a filesystem path to look up the key, you have a classic injection sink.

Mitigation. Treat kid as an opaque identifier. Look it up in an in-memory map or via a parameterised query. Reject any kid that doesn't match a strict allow-list pattern (e.g. ^[A-Za-z0-9_-]{1,64}$).

jku / x5u URL abuse

High

The JOSE spec lets a token reference its own verification keys by URL. A verifier that fetches whatever URL the token says will validate against an attacker-supplied key.

Mitigation. Ignore jku, x5u, jwk, and x5c from incoming tokens unless you absolutely need them. If you do, restrict to a hard-coded host allow-list and never follow redirects.
Baseline

Defense principles

If you remember nothing else, remember these five.

Trust the verifier configuration, not the token

Algorithm, issuer, audience, and key are decided by your service. The token never gets to choose.

Short lives, frequent rotation

A 15-minute access token limits damage from a leak to 15 minutes.

Keys in a secrets manager

Never in code, env files, or container images. Rotate on a schedule and on every suspicious event.

Audit every verification

Log algorithm, iss, sub, and result of each verify. Alert on spikes of failures or unknown kid.

Use a maintained library

Don't hand-roll signature validation. Pick a library with active maintenance and a security advisory history.