How to Fix the JWT None Algorithm Attack

Setting alg=none in a JWT header tells the library to skip signature verification entirely - so an attacker can forge any payload they like without knowing the secret. The fix is a single argument to jwt.decode(): an explicit algorithms allowlist.

Security Engineerpythonjwtauthentication

How the none algorithm attack works

A JWT has three base64url-encoded parts: header.payload.signature. The header declares which algorithm was used to sign the token:

{"alg": "HS256", "typ": "JWT"}

The alg: "none" variant tells the library "this token is unsecured - no signature required." The spec allows it for tokens transmitted over a secure channel where both parties already trust each other. In practice, a vulnerable jwt.decode() call will honor alg: "none" even when that was never the application's intent.

An attacker crafts a token by hand: take any valid payload (e.g. {"sub": "admin", "role": "superuser"}), base64url-encode it, change the header's alg to "none", and omit the signature (or append an empty string after the final dot). The server accepts it as legitimate:

import jwt, base64, json

# Forge a token with alg=none
header = base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()).rstrip(b"=")
payload = base64.urlsafe_b64encode(json.dumps({"sub": "admin", "role": "superuser"}).encode()).rstrip(b"=")
forged = header.decode() + "." + payload.decode() + "."  # no signature

# On a vulnerable server this verifies successfully:
# decoded = jwt.decode(forged, "secret", algorithms=["HS256", "none"])

The vulnerable decode call

The root cause is calling jwt.decode() without an explicit algorithms parameter, or by including "none" in the list:

# VULNERABLE - accepts any algorithm the token claims, including "none"
data = jwt.decode(token, SECRET_KEY, algorithms=["HS256", "none"])

Without restriction, whatever algorithm the token header declares is trusted.

The fix: lock to an explicit allowlist

Pass only the algorithm your application actually uses:

# FIXED - rejects any token whose header declares a different alg
data = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])

With algorithms=["HS256"], PyJWT raises jwt.exceptions.InvalidAlgorithmError for any token that declares "none", "RS256", or anything else outside the list. Existing valid HS256 tokens continue to work unchanged.

PyJWT version notes

PyJWT 2.0+ made the algorithms argument required - omitting it raises a DecodeError at startup rather than silently accepting any algorithm. If you are on an older version, you must pass the argument explicitly. Either way, never include "none" in the allowlist outside of a deliberate, sandboxed use case.

Defense-in-depth

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

What is the JWT none algorithm attack?

An attacker crafts a token with alg=none in the header and no signature. A vulnerable jwt.decode() call that doesn't restrict algorithms accepts it as valid, granting the attacker any claims they write into the payload - without knowing the secret key.

How do I fix the JWT none algorithm vulnerability in Python?

Pass a strict algorithms allowlist to jwt.decode(): jwt.decode(token, SECRET_KEY, algorithms=["HS256"]). This causes PyJWT to reject any token whose header declares a different algorithm, including none.

Does upgrading PyJWT fix the none algorithm attack?

PyJWT 2.0+ requires the algorithms argument, so omitting it raises an error rather than silently accepting any algorithm. But if you're including 'none' in an old allowlist, upgrading alone won't help - you need to remove 'none' from the list.

Keep learning

Close a SQL Injection in a Search EndpointSecurity projectNeutralize a Stored XSS in CommentsSecurity projectRemove Hardcoded Credentials From Source CodeSecurity projectSecurity roadmapStep by step to hiredSecurity interview questionsSTAR answersAll Security projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →