JWT Attacks: The Complete Practical Guide
JWT alg:none and Flawed Signature Verification
How the none algorithm and missing signature checks let you forge JWTs: alg:none case variants, empty signatures, and servers that decode but never verify.
Published
The cheapest JWT attack is to make the server skip the signature check entirely. Two flavours do this: the none algorithm, which tells the server the token is unsigned, and servers that decode a token but never verify it. Both turn editing a token into forging one, and both are the first things to test on any JWT. Try it on a free live target.
What verification is supposed to guarantee
When the server issued your token it signed the header and payload with a key only it holds. Verification recomputes or checks that signature on the way back in. If it matches, the server knows the token is the one it issued and nobody altered the claims. That single check is the only thing standing between "I edited my role to admin" and "the server believes I am admin." Remove it and the token is pure attacker input.
The none algorithm
The JWT standard defines none as a valid algorithm meaning "unsigned." A server that decides how to verify from the token's own alg header will, when it sees none, skip the signature check and trust the claims. So the attack is:
- Decode the token.
- Change the claim you care about, for example
"role": "admin". - Set the header to
{"alg":"none","typ":"JWT"}. - Re-encode header and payload, and send an empty signature, keeping the trailing dot.
A finished none token looks like this, with nothing after the final dot:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.Decode the two parts and you can see what it carries:
{ "alg": "none", "typ": "JWT" }
{ "sub": "admin", "role": "admin" }The trailing dot matters. The token is still three parts, the third is just empty.
Case variants to dodge weak filters
A common half-fix is a filter that rejects the exact string none. It is easy to slip past if the check is not fully case-insensitive, because the JWT libraries often compare the algorithm case-insensitively while the filter does not. Try each of these:
{ "alg": "none" }
{ "alg": "None" }
{ "alg": "NONE" }
{ "alg": "nOnE" }If the server lower-cases the algorithm before using it but the blocklist only matched lowercase none, a mixed-case variant walks straight through.
Servers that never verify at all
The none algorithm is one way to skip verification, but some servers simply do not verify. They decode the token, read the claims, and trust them, regardless of the algorithm or signature. This is common in hand-rolled token handling where a developer called the decode function instead of the verify function.
Test it directly: take a normal token and corrupt the signature, or replace the third part with random base64url, keeping the real header and payload:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiJ9.not_a_real_signatureIf the server still accepts it, verification is absent and you can edit any claim you like without touching the algorithm. This is worth a dedicated test even when none is blocked, because it is a different bug with the same payoff.
Automating it
A proxy or a tool makes this instant. jwt_tool has a dedicated exploit mode that rebuilds the token with alg:none and an empty signature for you:
python3 jwt_tool.py <TOKEN> -X aThe -X a mode switches the algorithm to none and strips the signature, so you only edit the claims. In Burp, the JWT editor does the same from the proxy, covered in working with JWTs in Burp.
Confirming and reading the result
The confirmation is behavioural: the server now treats you as the edited claim. That might be a dashboard you could not reach, an admin-only field in the response, or a different authorization decision. If the forged token is rejected, move on to the key attacks: crack the secret in weak secret brute force, or if the token is RS256, try algorithm confusion.
Fixing it
- Pin the algorithm server-side and reject anything that is not on your short allow-list. Never let the token's
algchoose the verification path. - Reject
noneexplicitly. There is no reason to accept an unsigned session token. - Call the verify function, not the decode function, and fail closed if the signature cannot be checked.
Full per-library guidance is in preventing JWT attacks. Next, crack a weak signing key in weak secret brute force, or forge a none token now on a free target.