← All news
security6 min read

JWT algorithm confusion attacks and how AI code enables them

By Seaworthy · June 25, 2026

Close-up of a metal padlock with a glowing circuit board pattern, representing cryptographic security

Photo by FlyD on Unsplash

JWT algorithm confusion attacks and how AI code enables them

JWT verification has one particularly nasty failure mode. It is simple to introduce, easy to miss in code review, and lets an attacker forge tokens that your server happily accepts. The flaw is called algorithm confusion, and AI-generated authentication code produces it constantly.

What the attack does

A JWT has three parts: header, payload, signature. The header includes an alg field that names the signing algorithm used: RS256, HS256, none, and so on.

The attack works like this. Your server was set up to verify tokens using RS256, an asymmetric algorithm. The public key is public, so the attacker has it. They craft a new token, switch the alg field in the header to HS256 (a symmetric algorithm), and sign the token using your public key as the HMAC secret. Then they send it.

If your verification code reads the algorithm from the token header and passes it to the verify function, the library obliges. It checks the HS256 signature against the public key. The signature matches. The token is valid. The attacker is now whoever they claimed to be.

This is not a vulnerability in the JWT libraries. It is a vulnerability in how you call them.

Why AI code gets this wrong

Ask an LLM to write JWT verification middleware for Node.js and you will often get something like this:

const jwt = require('jsonwebtoken');

function verifyToken(token) {
  // Decode header to get algorithm
  const decoded = jwt.decode(token, { complete: true });
  const algorithm = decoded.header.alg;

  return jwt.verify(token, getPublicKey(), { algorithms: [algorithm] });
}

The intent is harmless. The LLM is trying to be flexible. But it has just handed the attacker control over which algorithm gets used to verify their own token.

The pattern shows up because training data is full of Stack Overflow answers and tutorial posts that do the same thing. Those posts were written to explain how JWT works, not to model production-safe verification. The AI reproduces the shape of the code without the security context.

The fix is one line

Stop reading the algorithm from the token. Hardcode it.

const jwt = require('jsonwebtoken');

const EXPECTED_ALGORITHM = 'RS256';
const PUBLIC_KEY = process.env.JWT_PUBLIC_KEY;

function verifyToken(token) {
  // Never read alg from the token header.
  // Hardcode the algorithm your server issued with.
  return jwt.verify(token, PUBLIC_KEY, { algorithms: [EXPECTED_ALGORITHM] });
}

That is it. jsonwebtoken will reject any token whose header alg does not match RS256, regardless of what the attacker puts there. If you are using a different library, check that it exposes an algorithms allowlist option and that you are passing it.

Watch out for the none algorithm too. Some older libraries accepted alg: none and skipped signature verification entirely. Modern libraries like jsonwebtoken reject it by default, but only if you do not explicitly allow it. If you see algorithms: ['RS256', 'none'] anywhere, that is an immediate incident.

How to audit your own code

Search your codebase for calls to your JWT library's verify function. Check every call site for an explicit algorithms option. If it is missing, the library may fall back to trusting the header. If it is present but populated from a decoded header field rather than a constant, you have the vulnerability.

# For jsonwebtoken users: find every jwt.verify call
grep -rn "jwt\.verify" src/

Then read each match. You are looking for any case where the algorithm is derived at runtime rather than set as a literal string or environment-configured constant.

If you use a framework that wraps JWT internally, like Passport.js with passport-jwt, check the strategy configuration. The algorithms array belongs in your JwtStrategy options, not downstream.

Symmetric vs asymmetric matters here

The attack is most damaging when you use asymmetric keys (RS256, ES256) because the public key is, by definition, not secret. When using symmetric keys (HS256), the secret is private, so an attacker cannot use it to forge a new signature. That does not make HS256 immune to other attacks, but algorithm confusion specifically exploits the public nature of asymmetric keys.

If you are building a service that issues JWTs for others to verify, use RS256 or ES256. Keep the private key secret. Publish the public key. And on the verification side, pin the algorithm.

What to check in generated code

When an AI writes auth middleware for you, treat the JWT verification call with the same suspicion you would give to a raw SQL query. Look for:

  • The algorithms option being absent from the verify call
  • The algorithm being decoded from the token header before verification
  • Any reference to alg: 'none' or algorithms: ['none']
  • The public key being fetched from an endpoint that the attacker could influence

Generated code gets the happy path right almost every time. It is the trust boundaries that it routinely gets wrong.

Seaworthy's static analysis checks for JWT verification calls that are missing an explicit algorithm allowlist or that derive the algorithm from untrusted token data.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode