ASVS 5.0 V9.1.2 requires that only algorithms on an allowlist can be used to create and verify self-contained tokens in a given context. The list names the permitted algorithms, ideally all symmetric or all asymmetric, and must not include none. If you need both kinds, extra controls must prevent key confusion. It is a Level 1 requirement.
Why it matters
A JWT header carries an alg field, and the attacker controls it. If the verifier believes the header, two classic attacks open up.
- The
noneattack: the token says no signature is needed, and a naive library agrees. - Algorithm confusion: your server expects RS256 and verifies with an RSA public key. The attacker sets
algto HS256 and signs using your public key as the HMAC secret. Public keys are public, so the forged token verifies.
How to implement it
The server decides the algorithm. The token never does.
await jwtVerify(token, key, {
algorithms: ["ES256"], // one entry, set in code or config
issuer: ISSUER,
audience: AUDIENCE,
});
jwt.decode(token, public_key, algorithms=["ES256"], audience=AUD, issuer=ISS)
Practical rules:
- Pick one algorithm per issuer and context. Asymmetric (RS256, ES256, EdDSA) is the better default when many services verify, because verifiers hold no signing secret.
- Keep the list in configuration with a test that fails if
noneappears. - Bind each key to its algorithm. In a JWKS, use the key's
algandusefields, and have your code refuse a key that does not match. - If you truly must accept both symmetric and asymmetric tokens, use separate verifiers with separate keys, chosen by issuer, never by the token header.
- Never pass a public key into a function that accepts an HMAC secret.
How to test it
- Resign a valid token with
alg: noneand an empty signature. Expect 401. - Take your public key, use it as an HS256 secret, and sign a forged token with
alg: HS256. Expect 401. - Use an algorithm that is secure but not on your list (for example RS512 when you allow RS256). Expect 401.
- Add a unit test that reads the configured list and asserts it contains no
noneand no mix of families.
Common mistakes
- Calling verify without an
algorithmsargument and trusting library defaults. - Letting a config flag add
HS256"for local development" to production. - Reusing one key variable for both RSA public keys and HMAC secrets.
- Using different libraries in different services with different defaults.
Quick check
Who should decide which algorithm verifies a token?
The verifying service, through a fixed allowlist. The token header is attacker controlled input.
Why do public keys matter in the HS256 attack?
If the verifier uses a public key as an HMAC secret, anyone who knows that public key can forge valid tokens.