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
CriticalThe 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.
none by default; make sure yours does.Algorithm confusion (RS256 → HS256)
CriticalIf 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.
Weak or guessable HS256 secrets
HighAn 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.
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
HighIf 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.
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
MediumCookies 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.
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
HighA stolen JWT remains valid until exp — there is no built-in "logout". Long-lived tokens turn a brief compromise into an extended foothold.
jtis for force-revocation. On material events (password change, suspicious login) rotate the signing key.kid injection (SQLi, path traversal)
MediumThe 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.
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
HighThe 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.
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.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.