Many web apps hand you a token after you log in, and that token is what proves who you are on every later request. When the server checks that token the wrong way, an attacker can rewrite what it says and walk in as someone else. JWT algorithm confusion is the name for a family of bugs where the server lets the token itself decide how it should be verified, and that one mistake turns a signed proof of identity into a form the attacker can fill in.
How a JSON Web Token is built
A JWT is three parts joined by dots: header.payload.signature. Each of the first two parts is a JSON object encoded with base64url, which is just a text safe way to carry bytes. The third part is a signature computed over the first two.
Say a fictional app called Acme Notes issues this token when you log in. Decoded, the header and payload look like this:
header
{ "alg": "RS256", "typ": "JWT" }
payload
{ "sub": "1042", "role": "user", "exp": 1893456000 }
The header says which algorithm signed the token. Here it is RS256, which uses a private RSA key to sign and the matching public key to verify. The payload holds claims: this user has id 1042 and the role user. The signature is the part that is supposed to make the payload impossible to change, because only Acme Notes holds the private key that can produce a valid one.
The whole point is trust. When Acme Notes gets a token back, it verifies the signature. If it checks out, the server believes the claims and lets you read your notes. Change one character in the payload and the signature no longer matches, so the token is rejected. That is the design working as intended.
Why jwt algorithm confusion happens at all
Here is the root cause. The alg field lives inside the header, and the header is part of the token the client sends, so the attacker controls it. If the verifying code reads alg from the token and trusts it to pick the verification method, the attacker gets to choose how their own token is checked. That is the whole bug in one sentence: the thing being verified is telling the verifier how to verify it.
A safe server ignores what the token claims and pins the algorithm on its own side. A vulnerable server asks the token what to do. Two named variants of this show up again and again.
The “alg”: “none” acceptance bug
Early JWT libraries supported an algorithm literally called none, for cases where a token was already protected some other way. A token using it has an empty signature. So the attacker takes a real token, sets the header to { "alg": "none", "typ": "JWT" }, edits the payload freely, and drops the signature, keeping the trailing dot:
{ "alg": "none" }.{ "sub": "1042", "role": "admin" }.
If the server sees none and concludes there is nothing to verify, it accepts the token and reads the claims as gospel. The attacker just changed "role": "user" to "role": "admin" with no key, no signature, and no secret. This is a plain authentication and authorization bypass.
The RS256 to HS256 key confusion
This one is quieter and it catches teams that think they did the right thing. RS256 is asymmetric: sign with a private key, verify with a public key, and that public key is meant to be shared openly. HS256 is symmetric: the same secret both signs and verifies.
Now picture a verify call that reads the algorithm from the token and passes in the RSA public key as the key material. When the token says RS256, the library treats that key as a public key and all is well. But if the attacker changes the header to HS256, some libraries then treat the same bytes as an HMAC secret. The public key is not secret. The attacker already has it, or can often fetch it from a public endpoint. So they compute a valid HS256 signature over any payload they like, using the public key as the HMAC secret, and the server verifies it against that same key and accepts it.
attacker starts from
header { "alg": "RS256" }
payload { "sub": "1042", "role": "user" }
attacker sends
header { "alg": "HS256" }
payload { "sub": "7", "role": "admin" }
signature = HMAC_SHA256(header.payload, RSA_public_key)
The forged token is signed with a key everyone is allowed to have. The claims now say the attacker is user 7 with the admin role, and the server has no reason to doubt it, because the signature is valid under the only key it uses.
The signature was never the weak point. The weak point was letting the token choose which kind of signature it was.
What the forged claims actually buy
Once the attacker can rewrite claims and still pass verification, the token becomes an editable identity card. A few common results:
- Privilege escalation. Flip
"role": "user"to"role": "admin"and reach pages and actions meant for staff. - Account takeover by id. Change
"sub"from your own id to another user’s and read or change their data. - Longer sessions. Push
"exp"far into the future so a token never expires. - Tenant crossing. In a multi tenant SaaS app, edit an organization id claim to see another customer’s records.
None of this touches a password. The login step was fine. The failure is entirely in how the server decided to believe the token on the way back in. This is why these bugs sit squarely in the access control space rather than in cryptography. The math held. The trust decision did not.
How to spot it and shut it down
Defending against jwt algorithm confusion is short work, and none of it is optional. Each step removes the attacker’s ability to pick the rules.
- Pin the algorithm on the server. Tell your verify call exactly which algorithm to accept, for example RS256 only, and reject anything else. Do not read
algfrom the token to decide. This is the single most important fix. - Reject “none” outright. Never allow an unsigned token in a system that relies on signatures. Most modern libraries block it by default, but confirm it rather than assume it.
- Keep the key types apart. The code path that verifies asymmetric tokens should be physically unable to fall back to using a public key as an HMAC secret. Separate keys, separate functions, no shared entry point that switches on the header.
- Use a maintained library and read its verify options. Ask it for an explicit allow list of algorithms. Older versions of several libraries had exactly these gaps.
- Test the tampering directly. Send a token with
algset tonone, and send an RS256 token re signed with the public key under HS256. A correct server rejects both. If either gets in, you have the bug.
The clean rule to remember: the token is input from the client, and input never gets to choose how it is checked. The server picks the algorithm and holds the keys, and the token only carries claims that survive that fixed check.
The assumption underneath
Strip these two attacks down and the same shaky belief is left. The server assumed the token would honestly describe how to verify it. An attacker who controls the header simply lies. Pinning the algorithm and separating keys removes the lie’s payoff. This is exactly the kind of trust assumption an autonomous researcher is built to test, by asking what a system takes on faith and whether that faith holds when someone edits the part they were handed. An early signal we find encouraging: a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. Read more on our about page.
Frequently asked questions
What is a JWT algorithm confusion attack?
It is an authentication bypass where the server trusts the token’s own alg header to decide how to verify the signature. An attacker changes alg so the server verifies a token the attacker can forge, then edits claims like the user id or role.
What is the alg none attack?
Some libraries accept a token with alg set to none and no signature at all. If the server does not reject it, an attacker can send an unsigned token with any claims and be trusted as any user.
What is RS256 to HS256 key confusion?
RS256 verifies with a public key. If an attacker switches alg to HS256, a vulnerable server verifies the token using that public key as the HMAC secret. The public key is not secret, so the attacker can sign a forged token that passes.
How do you prevent JWT algorithm confusion?
Pin the expected algorithm on the server instead of trusting the token header, reject none, and keep separate keys so a verification key can never be used as a signing secret.
Put an autonomous researcher on your own systems
UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.
