Build1 publisher2 min readPublished
Passing $header['alg'] to the verifier lets the token choose which algorithm checks it
A JWT signature only proves integrity when the verifier and the application already agree on algorithm, key and validity window, and in the vulnerable pattern all three are settled by input the attacker sent.
The Engineer · Build desk
![Illustration accompanying Passing $header['alg'] to the verifier lets the token choose which algorithm checks it](/_next/image?url=https%3A%2F%2Fmedia.theclarity.today%2Fnews%2Fheroes%2F2026-09-17%2Fbuild%2Fpassing-header-alg-to-the-verifier-lets-the-token-choose-which-algorithm-checks-1052454936-f31bfa4064344d36.png&w=3840&q=75)
What happened
- A dev.to writeup argues that a JWT signature establishes integrity only if the verifier and the application agree on which algorithm was used, which key should verify it, and whether the token is still valid.
- Because the header carries its own algorithm identifier, the server has to decide whether that algorithm is acceptable instead of accepting whatever the token names.
- In the vulnerable pattern the application decodes the header, sees alg set to none, and returns the decoded payload without running a signature check at all.
- The post's remedy is that the application determines the accepted algorithm server-side, so the token never gets a vote in how it is verified.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure JWT verification sits on the boundary for API authentication, SSO, mobile sessions, password resets and service-to-service calls, so a single call site that trusts the header is reachable from every one of those flows.
- constraint Until verification succeeds the payload is attacker-controlled input, so a role check or an expiry check read from a decoded claim before that point has no security value.
- decision With explicit algorithm and key configuration now part of library APIs, the code worth reviewing is the application's own decode call sites, not the dependency version in the lockfile.
The two decode calls in the post differ by one array lookup. In the safe one, the second argument to `new Key(...)` is the literal string `'HS256'`, written in the application's own source [10]. In the unsafe one it is `$header['alg']`, read straight out of the segment the client sent [18]. Both return a decoded payload [1]. The library is not being tricked. It verifies with the algorithm and the key the call handed it.
How bad that gets depends on the key types. With RS256 the issuer signs with a private key and the verifier checks the signature with the corresponding public key [12]. That public key is not meant to be secret, and it may be published through a JWKS endpoint [13]. The vulnerable shape has two conditions: the token influences which algorithm is selected, and the verification code is willing to use the same configured key material across incompatible algorithm families [14]. Change RSA verification to HMAC verification on an attacker-controlled `alg`, and the RSA public key may be interpreted as an HMAC secret, so an attacker who knows that public value could construct an HMAC-signed token [15]. I would keep the accepted algorithm in configuration next to the key it belongs with, so that rotating a key cannot widen the set of algorithms that verify.
The `none` case is blunter. JWT defines `none` as an algorithm meaning the token has no digital signature, which the post says can be legitimate in narrowly controlled scenarios [5][6]. What gets submitted is a modified header and payload with an empty third segment [8]. The first two segments are encoded, not encrypted, so the sample token in the post decodes for anyone holding it to a payload containing `"admin": true` [2]. The post says the important security flaw "isn't the existence of none" but "allowing untrusted token input to determine whether signature verification happens at all" [9].
The subheading names five failures: algorithm confusion, the `none` algorithm, weak signing secrets, missing expiration checks, and improper claim validation [19]. The published text stops inside the algorithm-confusion example, so the sections on weak secrets, expiry and claim validation are not in the excerpt.
What to watch
- The rest of the post: its subheading promises weak signing secrets, missing expiration checks and claim validation, and the published text stops before them.
- Whether any mainstream JWT library removes the ability to pass a token-derived algorithm into its verifier API at all.
- Advisories naming a shipped product where the RSA-to-HMAC swap was reachable from an unauthenticated request.