hmmnm.com — JWT security pitfalls: a token split into header, payload and signature bars, the signature struck through as tampered

JWT Security: alg=none, Key Confusion and Why the Header Lies

📋 Key Takeaways
  • Anatomy: What's Actually in a JWT
  • The Classic Two: alg=none and Key Confusion
  • The Modern Variants
  • Defense: The Six-Line Review Checklist
  • Where JWTs Sit in a Modern Design
8 min read · 1,501 words
Educational & Ethical Use Only — This article is provided for educational and ethical cybersecurity research purposes only. The techniques described should only be used on systems you own or have explicit permission to test. Always follow responsible disclosure and the laws applicable to you. Mitigations are included so engineers can harden real systems.
hmmnm.com — JWT security pitfalls: a token split into header, payload and signature bars, the signature struck through as tampered

TL;DR — A JWT is three base64url parts — header, payload, signature — and the most damaging vulnerabilities in its history all live in the first seven bytes of the header. When a verifier lets the token’s own alg field decide how to verify, attackers flip the algorithm to none (drop the signature) or to HMAC and sign with the RSA public key (key confusion) — both demonstrated across libraries in Tim McLean’s 2015 survey and both still found in the wild a decade later. RFC 8725 wrote the rule down explicitly: pin your algorithms server-side, validate every input, and never trust a claim from the object whose authenticity you’re establishing. Here’s the full anatomy of the failure class — and the modern variants (jwk injection, kid traversal) that grew from the same root.

JSON Web Tokens became the default stateless session and authorization format not because they were the most secure option, but because they were the most convenient: self-describing, cross-service, and verifiable without a shared session store. That convenience put a cryptographer’s decision — “which algorithm, which key, is this authentic?” — behind a header field that attackers control. The result is a vulnerability class where the protocol is fine and the trust flow is wrong, the same structural theme as the unauthenticated data sources we keep returning to on this site.

Anatomy: What’s Actually in a JWT

Per RFC 7519, a JWT is header.payload.signature, each part base64url-encoded:

Part Contents Who controls it
Header alg (algorithm), optionally kid (key id), jwk/jku/x5u (key material hints), typ The attacker, before verification
Payload Claims: issuer, subject, expiry, roles — whatever the app put there The attacker, before verification
Signature MAC or digital signature over the first two parts Whoever holds the key — if verification actually happens as intended

The entire security model rests on one question the verifier must answer correctly: given this token, which verification do I perform, with which key? Every attack below is a different way of making the verifier answer that question on the attacker’s terms.

The Classic Two: alg=none and Key Confusion

alg: none. The JOSE spec allows unsigned tokens; a library that reads the token’s alg header and dispatches on it will, when handed "alg":"none", skip signature verification entirely. An attacker edits the payload (admin: true), sets the header’s algorithm to none, and the token verifies. It sounds absurd; it shipped in major libraries across languages for years and still appears in real applications and CTF-style security labs today.

Key confusion (RS256 → HS256). The server signs with RSA (RS256) and holds both keys. The attacker rewrites the header to claim HS256 — HMAC — and signs the token with the RSA public key, which is public. A verifier that dispatches on the token’s declared algorithm now performs HMAC verification using the public key as the shared secret: it matches, the token verifies, and the attacker can mint tokens at will. Both flaws, and their root cause — libraries trusting the token header for verification strategy — were catalogued in Tim McLean’s March 2015 survey of JWT libraries across ecosystems, which remains the reference incident for this class.

The lesson generalized past JWT: attacker-controlled input must never select the verification mechanism. That is RFC 8725’s §3.1 (“perform algorithm verification”) in one sentence, and it applies equally to any “what kind of assertion is this?” dispatch in your code. Note the asymmetry that makes the class durable: for defenders, correct dispatch must hold on every request forever; for attackers, one confused code path, once, on one endpoint, is enough — which is why the fix belongs in the library’s default configuration, not in each application’s review vigilance.

The Modern Variants

  • kid manipulation. The header’s key-id selects which verification key to load. Implementations that build a path or SQL from kid have fallen to traversal (kid: ../../dev/null — an empty key) and injection. The field is input, and it must be treated as hostile input.
  • jwk / jku / x5u injection. Headers that let the token carry its own key or point to a key URL. A verifier that fetches or adopts keys from the token is asking the forged document which pen certified it — a category error with a CVE list attached.
  • Weak HMAC secrets. HS256 with a guessable secret is a cracking exercise: hashcat and jwt_tool crack offline token signatures at dictionary speed. A JWT’s “stateless” convenience quietly assumes your secret has password-cracking-grade entropy.
  • Expired-but-accepted tokens. The failure mode that needs no cryptography at all: verifiers that check the signature but never the exp/nbf claims keep dead sessions alive forever — a bug rate high enough that claim validation gets its own bullet in RFC 8725.

Defense: The Six-Line Review Checklist

  1. Pin the algorithm server-side — accept exactly one, hard-coded; reject everything else before parsing anything else. (RFC 8725 §3.1.)
  2. Never use token-supplied key material — ignore jwk/jku/x5u unless your design explicitly, deliberately uses them with an allowlisted key source.
  3. Treat kid as hostile string input — exact-match against a server-side registry; no path construction, no SQL, no shell.
  4. Validate all claims you rely onexp, nbf, iss, aud — and reject unknown-token-shape inputs loudly.
  5. Give HMAC secrets real entropy — 256 bits from a CSPRNG, rotated via your secrets process; if you can’t guarantee that, use asymmetric signatures where the verifier only ever holds a public key.
  6. Test the confusion paths explicitly — the fastest regression suite you’ll ever write: forge alg=none, HS256-signed-with-public-key, and expired tokens, and assert all three are rejected. This is detection-as-code thinking applied to your own auth edge — same discipline as the Sigma pipelines we’ve covered.

Where JWTs Sit in a Modern Design

Even with perfect verification, JWTs trade one risk for another: revocation. A stateless token is valid until it expires — there is no logout button on the math. Mature designs scope them narrowly: short-lived access tokens (minutes), server-side revocable refresh tokens, and immediate rejection lists only for the narrow high-value cases (admin claims). If your threat model needs instant revocation of long-lived bearer credentials, stateless JWTs are the wrong primitive regardless of algorithm hygiene — an architectural point worth more than any header check.

And when you need user presence rather than service-to-service assertions, the industry answer has moved to WebAuthn/passkeys, where a public key is bound to an origin and nothing bearer-able ever travels. JWTs remain excellent for service identity, short-lived delegation, and signed payloads — the machine equivalent of a badge that a gate can check offline; they remain a poor session store for humans, where logout and revocation are product requirements, not edge cases. Knowing which job to give them is most of the security, and no amount of algorithm pinning will fix having chosen the wrong job.

Key Takeaways

  • The historic JWT flaws — alg=none and RS256/HS256 key confusion — share one root cause: verifiers letting the token’s attacker-controlled header choose the verification algorithm (McLean, 2015).
  • RFC 8725 codified the fix: explicit algorithm allowlists, validated inputs, validated claims — never token-driven verification strategy.
  • Modern variants (kid traversal/injection, jwk/jku/x5u key injection, weak HMAC secrets, ignored exp) are the same trust-flow error in new clothes.
  • The cheapest defense is a three-forge regression suite: none-algorithm, key-confusion, and expired tokens must all fail verification — write it before you write the feature.
  • Architecture beats header hygiene: short lifetimes, revocable refresh, and WebAuthn for humans limit what any forged token can ever be worth.

FAQ

What is the alg=none attack?
Setting the JWT header’s algorithm to “none” so a header-driven verifier skips signature checking — letting the attacker edit the payload freely. The fix is refusing unsigned tokens and pinning one expected algorithm.

What is JWT key confusion?
Changing a token’s declared algorithm from RSA (RS256) to HMAC (HS256) and signing with the service’s public key, which a confused verifier accepts as the HMAC secret. The fix: never dispatch verification on the token’s own alg field.

Is JWT itself broken?
No — the signature schemes are standard and sound. The repeated failures are implementation trust-flow bugs, which is why RFC 8725’s best-practices document targets verifier behavior, not the crypto.

Are jwk, jku and x5u headers safe to honor?
Only with an explicit, allowlisted key source. Letting a token name or carry its own verification key is asking the possibly-forged document to supply its own notary.

How long should JWT lifetimes be?
Access tokens: minutes. Anything longer needs a revocation story, and JWTs structurally can’t offer one — pair them with revocable refresh tokens or accept the gap knowingly.

How do I test my implementation quickly?
Forge three tokens — alg=none, HS256 signed with your public key, and a validly signed but expired one — and confirm all three are rejected. That suite catches the entire historic class in an afternoon.

References

  1. RFC 7519 — JSON Web Token (JWT)
  2. RFC 8725 — JSON Web Token Best Current Practices
  3. Tim McLean — Critical vulnerabilities in JSON Web Token libraries (2015)
  4. IETF — RFC 8725bis draft (updated JWT BCP)
  5. PortSwigger Web Security Academy — JWT
  6. ticarpi — jwt_tool (JWT testing toolkit)
  7. Curity — JWT security best practices
  8. Wikipedia — JSON Web Token

Current as of September 2026. Educational reference — test only applications you own or are authorized to assess.

Hmmnm
Published by Hmmnm

Hands-on cybersecurity tutorials, CVE breakdowns, and guided learning paths — written and lab-tested by the Hmmnm team.

🛡️ Hmmnm also delivers this expertise as a service — security testing, assessment & training.

Prabhu Kalyan Samal

Application Security Consultant at TCS. Certifications: CompTIA SecurityX, Burp Suite Certified Practitioner, Azure Security Engineer, Azure AI Engineer, Certified Red Team Operator, eWPTX v3, LPT, CompTIA PenTest+, Professional Cloud Security Engineer, SC-900, SC-200, PSPO I, CEH, Oracle Java SE 8, ISP, Six Sigma Green Belt, DELF, AutoCAD. Writing about ethical hacking, security tutorials, and tech education at Hmmnm.