Card 07/ 12
Validating a JWT, Check by Check
A server checks the algorithm, the signature, the issuer, the audience and the times, and refuses on the first failure.
A server validates a JWT by running a fixed list of checks and refusing the token at the first failure, with a 401. It checks that the algorithm is one it expects, verifies the signature with the issuer's published key, checks the issuer and audience are the expected ones, and checks the token is within its valid times. Only a token that passes every check is used to make an authorisation decision. Here are the checks applied to an example token whose payload names a user, an issuer and an audience.
When The header's alg is not on the server's allow list, for example none or HS256 where RS256 is expected
, chooseRefuse with 401
When The signature does not verify with the key the kid names in the issuer's JWKS
, chooseRefuse with 401
When iss is not https://auth.example.com, or aud does not include https://api.example.com
, chooseRefuse with 401
When exp has passed, or nbf is in the future, allowing a minute of clock skew
, chooseRefuse with 401
When Every check passes
, chooseAccept, and authorise from sub and scope
The checks, in order
Check the algorithm against an allow list
The server accepts only the algorithms it expects from this issuer, here RS256. A token claiming alg: none, which means unsigned, or a symmetric algorithm where an asymmetric one is expected, is refused before anything else.
Verify the signature with the issuer's key
The issuer publishes its public keys as a JWKS, a JSON Web Key Set, at a known URL. The server finds the key whose id matches kid, caches it, and verifies the signature over the first two parts.
Check the issuer and the audience
iss must be the expected issuer, and aud must name this API. A valid token from the same issuer but meant for another API is refused here.
Check the times
exp must be in the future and nbf, if present, in the past. A small allowance, around a minute, absorbs clocks that disagree.
Authorise from the claims
Only now does the server read sub and scope and decide whether this caller may make this request.
Why the signature comes before the claims. Until it is verified, every claim is whatever the sender typed. Reading sub or scope first, even for logging that drives a decision, trusts an attacker's input.
Use a library. A maintained JWT library performs these checks, but only those it is configured for. Pass the expected algorithm, issuer and audience explicitly; a library left to read alg from the token is the classic vulnerability.
← → move through sections and cards