Skip to content
Back to Blog
jwtsecurityvulnerabilityauthentication

JWT alg:none and Confusion Attacks Explained

Two classic JWT attacks, alg:none and RS256-to-HS256 confusion, both exploit servers that trust the alg header. This breaks down each attack step by step and the one-line allowlist fix that defeats them.

SZ
Founder, Molixa
10 min read
Share
JWT alg:none and Confusion Attacks Explained
Table of contents6 sections

The jwt none algorithm vulnerability lets an attacker strip a token's signature and rewrite its contents, and the server accepts the forged token as valid. It happens when your code trusts the alg field inside the token header instead of deciding the algorithm yourself. The same root cause powers the RS256-to-HS256 confusion attack. Both are auth bypasses, and both close with one line of code.

If you ship JSON Web Tokens, this is one of the few security bugs that turns a read-only nuisance into full account takeover. An attacker who can forge a valid token can set "sub": "admin", sign nothing, and walk in. Below you will see exactly how each attack works, with the bytes laid out, plus the allowlist fix that ends the entire class of bug.

What the JWT None Algorithm Vulnerability Actually Is#

A JWT has three parts separated by dots: a base64url header, a base64url payload, and a signature. The header declares which algorithm signed the token, like {"alg":"HS256","typ":"JWT"}. The signature is what proves the token has not been tampered with.

The none algorithm is a legitimate part of the JWT spec (RFC 7519). It means the token is unsigned. It exists for situations where the transport layer already guarantees integrity, so the signature is intentionally empty. The problem is not that none exists. The problem is when a verification library treats a token's own alg: none header as permission to skip signature checking entirely.

The core flaw in both attacks below is identical: the server reads the attacker-controlled alg header and trusts it to decide how (or whether) to verify the signature. Never let untrusted input choose your verification algorithm.

The forgery in three steps#

Here is the attack against a server that honors alg: none. Say a real token decodes to this payload:

{ "sub": "1234", "role": "user", "exp": 1735689600 }

An attacker does three things:

  1. Changes the header to {"alg":"none","typ":"JWT"} and base64url-encodes it.
  2. Edits the payload to {"sub":"1","role":"admin","exp":9999999999} and encodes it.
  3. Drops the signature, leaving the token ending in a trailing dot with nothing after it.

The forged token looks like eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxIiwicm9sZSI6ImFkbWluIn0. (note the dangling dot). A vulnerable server decodes the header, sees none, skips verification, and grants admin. No secret, no key, no cracking. You can paste any token into our free JWT decoder to see exactly which alg header it carries before you trust it.

Why "none" sometimes has a capital N#

Some early libraries case-sensitively blocked the string none but not None, NONE, or nOnE. Because the JWT spec treats algorithm names case-sensitively but real-world code did not always, attackers bypassed naive blocklists by varying the case. This is the first lesson of JWT security: a blocklist of bad values is fragile. An allowlist of good values is not.

The RS256-to-HS256 Algorithm Confusion Attack#

The confusion attack is sneakier and still catches teams in 2026. It exploits the difference between symmetric and asymmetric signing when a single verification call accepts both.

Two algorithm families matter here:

  • HS256 is symmetric. One shared secret both signs and verifies. Whoever holds the secret can mint valid tokens.
  • RS256 is asymmetric. A private key signs, and a public key verifies. The public key is, by design, public. It is fine to publish it.

A server using RS256 keeps its private key locked away and verifies incoming tokens with the public key. That is safe, as long as the server only ever runs RS256 verification.

How the public key becomes a forging secret#

The confusion happens when verification code looks roughly like this:

// VULNERABLE: algorithm taken from the token header
jwt.verify(token, key);

Many libraries, given a token, read the alg header to pick the verification path. If the original system is RS256, the attacker does this:

  1. Obtains the RSA public key. It is often exposed at a JWKS endpoint, in a .well-known URL, in an SDK, or simply published in docs.
  2. Forges a token with the header changed to {"alg":"HS256"} and an elevated payload.
  3. Signs that token with HMAC-SHA256, using the public key's exact bytes as the HMAC secret.

Now the server receives an HS256 token. It pulls out its RSA key (which it believed was only ever a verification key) and runs HMAC with it. Because the attacker signed with that same public-key string, the HMAC matches. The signature verifies. The forged admin token is accepted.

The attacker turned your harmless, published public key into the secret that forges tokens. They never needed the private key at all.

Warning: the trickiest part of reproducing this is getting the exact byte representation of the public key right (PEM with the trailing newline, headers included or stripped). Attackers brute-force the small number of common encodings. Treat any RS256 deployment that also accepts HS256 as already broken.

Why this is so easy to ship by accident#

The vulnerability is almost never written on purpose. It sneaks in when:

  • A library defaults to inferring the algorithm from the token header.
  • A team migrates from HS256 to RS256 but leaves the old verification path enabled "just in case."
  • A wrapper function accepts a generic key argument and passes it through to whatever algorithm the token requests.

If you want a deeper look at why decoding a token is not the same as verifying it, the breakdown in decode versus verify in JWT security shows where teams conflate the two and open exactly this hole.

The Fix: Allowlist Your Algorithm#

Both attacks die the moment the server stops trusting the token's alg header and instead pins the algorithm it will accept. This is the single most important JWT defense, and it is usually one line.

Pin the algorithm explicitly#

In Node with the common jsonwebtoken library, pass an explicit algorithms allowlist:

// SAFE: only HS256 is ever accepted
jwt.verify(token, secret, { algorithms: ["HS256"] });

// SAFE: only RS256, with the PUBLIC key, nothing else
jwt.verify(token, publicKey, { algorithms: ["RS256"] });

With this allowlist in place, a forged alg: none token is rejected because none is not in the list. A forged HS256 token sent to the RS256 verifier is rejected because the verifier will only run RS256, and HMAC bytes cannot satisfy an RSA signature check.

Every mature JWT library has the same control under a slightly different name:

Language / LibraryThe allowlist parameter
Node jsonwebtokenalgorithms: ["RS256"] in verify()
Python PyJWTalgorithms=["RS256"] in jwt.decode()
Go golang-jwtWithValidMethods([]string{"RS256"})
Java java-jwt (Auth0)build the verifier with the specific Algorithm
PHP firebase/php-jwtpass the algorithm to JWT::decode()

A short hardening checklist#

Pinning the algorithm is the headline fix. These habits close the rest of the gaps:

  • Never accept none in production. There is no reason a logged-in user's token should be unsigned.
  • Use one algorithm per service. If you do not need both HS256 and RS256, do not let your verifier accept both. Mixing them is what invites the confusion attack.
  • Keep verification keys typed. Load your RSA public key as a key object, not a raw string, so it cannot accidentally be used as an HMAC secret.
  • Validate claims, not just the signature. Check exp, iss, and aud. A valid signature on a token meant for another service is still a problem.
  • Inspect tokens you receive. Before integrating a third-party token, decode it and confirm the header and claims match what you expect.

You can confirm any of this against a real token in seconds. Our JWT decoder and security tool shows the header, payload, and which algorithm a token claims, so you can spot an alg: none or an unexpected HS256 before it reaches your verifier. The companion guide on reading JWT decoder output for token security walks through what each field tells you.

How to Tell If Your App Is Vulnerable#

You do not need a pentest team to find this. Three quick checks cover most real-world exposure.

Check 1: Is the algorithm pinned?#

Search your codebase for every call that verifies a token. If any verification path omits an algorithms allowlist (or its equivalent), that path trusts the token header and is suspect. This is the highest-value check, and it takes minutes.

Check 2: Does a none-token get through?#

Take a valid token, change its header to {"alg":"none","typ":"JWT"}, change a claim, drop the signature (keep the trailing dot), and send it to a protected endpoint. A 200 response with the forged identity confirms the vulnerability. A 401 confirms the path rejects it.

Check 3: Is your public key really public, and does anything accept HS256?#

If you sign with RS256, assume the public key is in an attacker's hands. The only thing standing between that and a forged token is whether any verifier in your stack will run HS256. If the answer is yes anywhere, you are exposed to the confusion attack until you pin RS256.

Frequently Asked Questions#

Is the JWT none algorithm vulnerability still relevant in 2026? Yes. Modern, maintained libraries reject alg: none by default and require an explicit allowlist, so a fresh install is usually safe. The bug persists in older library versions, in hand-rolled JWT code, and in apps that override defaults to "support flexibility." Auditing your verification calls is still worth doing.

Why is publishing my RS256 public key safe if it can forge tokens? The public key alone is harmless. It only becomes dangerous when your server is willing to run HS256 verification and treats that public key as an HMAC secret. Fix the willingness to accept HS256 (pin RS256), and publishing the public key is exactly as safe as the design intends.

What does "allowlist the algorithm" mean in practice? It means telling your verify function the exact algorithm or algorithms you accept, rather than letting it read the algorithm from the incoming token. In jsonwebtoken that is verify(token, key, { algorithms: ["RS256"] }). The verifier then ignores the token's alg header when deciding how to check the signature.

Can I just block the string "none" to stay safe? No. Blocklists are brittle. Attackers bypass case-sensitive blocks with None or NONE, and a blocklist does nothing against the RS256-to-HS256 confusion attack, which uses a perfectly normal HS256 value. An allowlist of accepted algorithms defends against both attacks at once.

How do I inspect what algorithm a token is using? Decode the token's first segment, which is the base64url-encoded header containing the alg field. You can do this manually or paste the token into a JWT decoder that shows the header, payload, and declared algorithm. Decoding never validates the signature, so treat the result as informational until your server verifies it with a pinned algorithm.

Does using a longer secret or stronger key fix this? No. A 4,096-bit RSA key does not help if the verifier can be tricked into HMAC mode, and a strong HMAC secret does not help if the server accepts unsigned none tokens. These attacks bypass the cryptography rather than break it, so the cure is pinning the algorithm, not increasing key strength.

The Bottom Line on the JWT None Algorithm Vulnerability#

The jwt none algorithm vulnerability and the RS256-to-HS256 confusion attack share one root cause: trusting attacker-controlled input to decide how a token is verified. Strip a signature or swap a public key in as an HMAC secret, and a server that reads its marching orders from the alg header will wave the forgery through.

Pin the algorithm with an explicit allowlist, refuse none in production, and keep one algorithm per service. That handful of habits retires the entire bug class. When you need to confirm what a token actually contains, decode it first and verify it second with a fixed algorithm, and you close the door for good.

jwtsecurityvulnerabilityauthentication

More from Molixa

Try Molixa Tools

50+ free AI tools for content creation, SEO, coding, and more. No signup, no watermark.

Explore all tools