When you verify a JWT, the token itself declares which algorithm signed it, and if you do not pin the algorithm your code will trust that declaration. An attacker can set the algorithm to none, or switch an RS256 token to HS256, to bypass verification and forge tokens. Always tell the verifier exactly which algorithm to accept.
Why it's a problem
With no pinned algorithm, a token claiming alg:none may be accepted with no signature at all, letting anyone forge any claims. The RS256-to-HS256 confusion attack is subtler: the attacker signs a token with your public key as if it were an HMAC secret, and a verifier that accepts either algorithm treats it as valid. Either way, your authentication is defeated.
The pattern
// trusts the algorithm the token declares const payload = jwt.verify(token, key);
The fix
// only accept the algorithm you actually use
const payload = jwt.verify(token, key, {
algorithms: ["RS256"],
});Why AI tools write this
The two-argument verify call is the shortest form and works for legitimate tokens, so it is the natural completion. Pinning the algorithm is an extra option that security requires but a functional test never needs, so it is easy for it to be left off.
The quick fix
- Pass an explicit algorithms allowlist to your JWT verify call.
- Never allow alg:none, and do not accept both HMAC and RSA algorithms on the same key.
- Verify the signature; never trust a decoded payload you have not verified.
Common questions
What is the alg:none attack on JWTs?
A crafted JWT sets the algorithm header to none and omits the signature. A verifier that does not pin the algorithm may accept it because there is nothing to validate, accepting any payload as legitimate.
What is the RS256-to-HS256 confusion attack?
If a system normally uses RS256, an attacker switches the alg header to HS256 and signs the token using your public key as the HMAC secret. A verifier that accepts both algorithms validates the HMAC with the public key and accepts the forged token.
Does the jsonwebtoken library protect against this by default?
Recent versions of jsonwebtoken reject alg:none by default but still require you to pass an explicit algorithms option for full protection. Always pass { algorithms: ['RS256'] } or whichever algorithm your system uses.