How to Fix a JWT Verification Bug (verify_signature: False)

Setting verify_signature: False in PyJWT turns off the entire point of a JWT - every token authenticates, including ones signed with a random key or using the alg=none exploit. The fix is two lines, but only if you know the pattern.

Backend Engineerjwtauthsecurity

What verify_signature: False actually does

PyJWT lets you decode without verifying for debugging purposes. When you pass:

jwt.decode(token, options={"verify_signature": False})

PyJWT skips all signature checks and returns the payload for any token - one signed with a random key, a token from a different service, and critically the alg=none forgery where no signature exists at all. If this is in your verify_jwt() function, authentication is completely bypassed.

The fix: always decode with the secret and algorithm list

Replace the unsafe call with a real decode that enforces the signature and pins the accepted algorithm to HS256:

import jwt

SECRET = "your-signing-key"  # in production: load from env or KMS

def verify_jwt(token: str):
    try:
        return jwt.decode(token, SECRET, algorithms=["HS256"])
    except jwt.PyJWTError:
        return None

Three things are doing the work here:

Why alg=none is dangerous

The alg=none exploit lets an attacker craft a token with a valid-looking payload, set the header to {"alg": "none"}, and send it with an empty signature. Libraries that do not enforce an algorithm allowlist will accept it as valid. This is a well-documented class of vulnerability that has affected production systems - the only defense is an explicit algorithms= list.

What production JWT validation also checks

The snippet above handles signature verification. A production verify_jwt should also validate:

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

Why does verify_signature: False bypass authentication?

It tells PyJWT to skip the HMAC check entirely - the library just decodes the payload without checking whether the signature matches your secret. Any token, including alg=none forgeries and tokens signed by attackers, will pass.

What is the alg=none JWT attack?

An attacker crafts a token with a valid-looking payload, sets the header algorithm to 'none', and omits the signature. Libraries without an algorithm allowlist accept it as valid. Passing algorithms=['HS256'] to jwt.decode blocks this attack.

How do I handle expired or malformed JWTs in Python?

Wrap jwt.decode in try/except jwt.PyJWTError - PyJWT raises a subclass of that base exception for expired tokens, bad signatures, wrong algorithms, and malformed tokens. Return None (or raise an HTTP 401) in the except block.

Keep learning

Return the Correct HTTP Status CodeBackend projectRepair Broken API PaginationBackend projectAdd CORS Middleware to an Express APIBackend projectBackend roadmapStep by step to hiredBackend interview questionsSTAR answersAll Backend 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 →