Validating a token, step by step
Seven checks, and what skipping each one lets an attacker do.
Open this lesson in the learning hubKey points
- Verify the signature first, using a key you chose - never one named in the token. A token you have not verified is attacker-controlled input, including its header.
- Pin the algorithm. Accepting whatever
algsays enables the historicnoneattack and the RS256-to-HS256 confusion where your public key is used as an HMAC secret. - Check exp, and allow a small clock skew - thirty seconds is typical. Without skew you get intermittent 401s that nobody can reproduce.
- Check iss against a pinned value. Otherwise anyone who can run an identity provider can mint tokens you accept.
- Check aud. This is the one most often skipped, and skipping it means a token issued for another service in the same estate works here.
- Then, and only then, read the claims. Authorisation decisions come after validation, never from an unverified token.
Example
// The order matters. Each step gates the next.
//
// 1. parse the structure header.payload.signature
// 2. select the key by kid, from YOUR trusted JWKS
// 3. verify the signature with a PINNED algorithm
// 4. check exp / nbf with ~30s clock skew
// 5. check iss against a pinned value
// 6. check aud is this token FOR me?
// 7. only now read the claims
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder
.withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
.jwsAlgorithm(SignatureAlgorithm.RS256) // PINNED - not from the token
.build();
decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new JwtIssuerValidator("https://auth.example.com"),
new JwtTimestampValidator(Duration.ofSeconds(30)),
audienceValidator("orders-api")));
return decoder;
}
static OAuth2TokenValidator<Jwt> audienceValidator(String expected) {
return jwt -> jwt.getAudience().contains(expected)
? OAuth2TokenValidatorResult.success()
: OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token", "Wrong audience", null));
}
/*
* WHAT EACH SKIPPED CHECK COSTS:
*
* signature anyone forges any token critical
* pinned alg alg:none, or RS256 -> HS256 confusion critical
* exp a leaked token never expires high
* iss any issuer you can reach is trusted critical
* aud a token for service B is accepted by A high
* clock skew intermittent 401s (not a hole, just pain)
*/
Verify the signature with a pinned algorithm before reading anything - an unverified token is attacker-controlled input.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JWT Authentication Course course, and every lesson in it is listed on the JWT Authentication Course contents page.