Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring JWT clock skew: bound tolerance without extending token trust blindly

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Clock tolerance can absorb small issuer and resource-server drift, but it also expands the effective acceptance window around token timestamps.

Name the clock problem

A newly issued receipt token can arrive at a resource server whose clock is slightly behind and appear not yet valid. A token near expiry can appear expired on a server ahead of the issuer. JwtTimestampValidator accepts a bounded skew for exp and nbf checks. Do not remove timestamp validation to solve clock drift. Issuer and audience validation still needs to run alongside it.

Compose validators, do not replace trust

If a custom NimbusJwtDecoder is installed to adjust skew, attach a validator that includes the issuer check and the timestamp check. Replacing the default validator with only JwtTimestampValidator can accidentally drop issuer validation. Keep the skew to the measured clock error plus a small margin, and monitor host time synchronization. A five-minute tolerance turns a five-minute access-token lifetime into a materially wider acceptance period near expiry.

Exercise boundaries

Generate tokens just before nbf, just after exp, with the wrong issuer and with the wrong audience. The first two should follow the chosen tolerance; the last two must fail regardless of time. Make tests use a controlled clock where possible rather than sleeping. Key rotation and timestamp tolerance are different windows: a still-valid token needs its signing key available, but a rotated key does not justify accepting expired tokens.

Implementation contract

Java
NimbusJwtDecoder decoder = NimbusJwtDecoder.withIssuerLocation(issuerLocation).build();
OAuth2TokenValidator<Jwt> validity = new DelegatingOAuth2TokenValidator<>(
    new JwtTimestampValidator(Duration.ofSeconds(37)),
    new JwtIssuerValidator(issuerLocation),
    new JwtClaimValidator<List<String>>("aud", audiences ->
        audiences != null && audiences.contains("receipt-api"))
);
decoder.setJwtValidator(validity);

Cost and verification

Timestamp checks are constant-time. A wider skew has little compute cost but increases the period in which a stolen or expired token may be accepted.

Common Mistakes

  • Do not turn off exp or nbf validation to hide unsynchronized clocks.
  • Do not replace the decoder validator with a timestamp-only validator and lose issuer checks.
  • Do not assume key-overlap and clock-skew settings solve the same failure.

Read next

Spring resource server JWT: validate both issuer and intended audience, Spring Resource Server JWT key rotation: overlap keys across token lifetime, Spring Security JWT resource server: validate trust before checking scope, Spring Security 401 versus 403: authentication and access are separate failures, Spring OAuth2 client credentials: acquire and reuse a service token.

spring
spring-security
jwt-clock-skew-window
Storage details