The JWT alg none attack
A JWT's security rests on its signature, but the token also tells the server which algorithm to use when checking that signature. Letting the attacker controlled token decide how it gets verified is the root of two classic forgery attacks. Both are old, both are well documented, and both still appear in real code because the mistake is easy to make.
The header controls verification
Every JWT begins with a header that names the signing algorithm.
{ "alg": "HS256", "typ": "JWT" }
A naive verifier reads alg from this header and uses whatever it finds to validate the token. That is the flaw. The header is part of the untrusted input, so an attacker can change it and influence how their own token is checked.
The alg none attack
The JWT specification defines an algorithm literally called none, meaning the token is unsigned. It exists for narrow cases where the transport is already trusted. The attack is to take a valid looking payload, set the header to {"alg":"none"}, and leave the signature empty.
header: {"alg":"none"}
payload: {"sub":"admin"}
signature: (empty)
A verifier that honors the header sees none, concludes no signature is required, and accepts a token that anyone could have written. The attacker has just granted themselves whatever the payload claims.
The HS versus RS confusion attack
The second attack is subtler and targets services that use asymmetric signing. With RS256, the server signs with a private key and verifies with a public key, and the public key is, by design, public. With HS256, the same secret both signs and verifies.
The attack flips a token's header from RS256 to HS256, then signs the forged token using the server's public key as if it were the HMAC secret. If the verifier reads the header and sees HS256, it uses the public key it has on file as the HMAC key, recomputes the signature, and it matches. The attacker needed only a value that was never meant to be secret. The differences between these two algorithms are covered in JWT HS256 vs RS256.
The fix: pin the algorithm
The defense for both is the same single rule: never trust the algorithm named in the token. The server already knows which algorithm it issued tokens with, so it should demand that one and reject everything else.
jwt.verify(token, key, { algorithms: ["RS256"] });
By pinning algorithms to an explicit allowlist, a token claiming none is rejected, and a token claiming HS256 is never verified with an RS key. Also reject none outright unless you have a specific, isolated reason to allow it, and keep your HMAC secrets and signing keys clearly separated so one can never be used as the other.
You can decode a token and read its header to see exactly which algorithm it claims in the JWT Decoder. It only decodes and verifies nothing against a secret, and it runs entirely in your browser so the token never leaves your device. To learn the fields inside the payload, see JWT claims explained.
The practical takeaway: the token should never choose how it is verified. Pin the expected algorithm on the server, reject none, and keep signing keys and secrets strictly separate, and both of these attacks disappear.