quarkusio/quarkus · error · InvalidJwtException
ISSUED_AT_INVALID_PAST
ISSUED_AT_INVALID_PAST
Error message
Token age exceeds the configured token age property
What it means
When quarkus.oidc.token.age is configured, Quarkus checks the 'iat' (issued-at) claim of verified JWTs: if now - iat exceeds the configured max age plus lifespan grace, an InvalidJwtException with ErrorCodes.ISSUED_AT_INVALID_PAST is thrown. This enforces a maximum token age regardless of the token's own exp.
Source
Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java:342
String newHeaders = new String(
Base64.getUrlEncoder().withoutPadding().encode(headers.toString().getBytes()),
StandardCharsets.UTF_8);
int dotIndex = token.indexOf('.');
String newToken = newHeaders + token.substring(dotIndex);
return newToken;
}
}
return token;
}
private void verifyTokenAge(Long iat) throws InvalidJwtException {
if (oidcConfig.token().age().isPresent() && iat != null) {
final long now = now() / 1000;
if (now - iat > oidcConfig.token().age().get().toSeconds() + getLifespanGrace()) {
final String errorMessage = "Token age exceeds the configured token age property";
LOG.warn(errorMessage);
throw new InvalidJwtException(errorMessage,
List.of(new ErrorCodeValidator.Error(ErrorCodes.ISSUED_AT_INVALID_PAST, errorMessage)), null);
}
}
}
public Uni<TokenVerificationResult> refreshJwksAndVerifyJwtToken(String token, boolean enforceAudienceVerification,
boolean subjectRequired, String nonce) {
return asymmetricKeyResolver.refresh().onItem()
.transformToUni(new Function<Void, Uni<? extends TokenVerificationResult>>() {
@Override
public Uni<? extends TokenVerificationResult> apply(Void v) {
try {
return Uni.createFrom()
.item(verifyJwtToken(token, enforceAudienceVerification, subjectRequired, nonce));
} catch (Throwable t) {
return Uni.createFrom().failure(t);
}View on GitHub (pinned to e1c734241f)
Solutions
- Increase quarkus.oidc.token.age to a value >= the IdP's access token lifetime.
- Remove quarkus.oidc.token.age if only exp-based expiry is desired.
- Increase quarkus.oidc.token.lifespan-grace to absorb clock skew.
- Synchronize clocks between IdP and application hosts.
Example fix
// before quarkus.oidc.token.age=1M // after quarkus.oidc.token.age=1H
Defensive patterns
Strategy: validation
Validate before calling
Long iat = jwt.getClaims().getIssuedAt();
long maxAgeSec = config.token().age().get().toSeconds();
if (iat != null && System.currentTimeMillis() / 1000 - iat > maxAgeSec + grace) {
// reject before calling the provider
} Type guard
boolean isWithinConfiguredAge(Claims claims, Duration maxAge, long grace) {
return claims.getIssuedAt() == null
|| System.currentTimeMillis() / 1000 - claims.getIssuedAt() <= maxAge.toSeconds() + grace;
} Try / catch
try {
return provider.verifyJwtToken(token, enforceAudience);
} catch (InvalidJwtException e) {
if (e.hasError(ErrorCodes.ISSUED_AT_INVALID_PAST)) { /* refresh token */ }
throw e;
} Prevention
- Set quarkus.oidc.token.age >= the IdP's access-token lifetime
- Don't set token.age tighter than the provider's own exp
- Add lifespan-grace to absorb clock skew
- Keep iat accurate in tokens minted by your own test tooling
When it happens
Trigger: A JWT whose iat is older than quarkus.oidc.token.age (e.g. long-lived tokens or tokens with missing/wrong iat), verified by verifyJwtTokenInternal or introspection age check.
Common situations: Setting a very small token age (e.g. 1m) while the IdP issues tokens with lifetimes of hours; clock skew making iat appear older; test tokens with hardcoded old iat values.
Related errors
- Cannot get token for tenant '%s' because a %s client_asserti
- Only one of client secret or JWT secret authentication metho
- Only one of JWT secret or JWT private key authentication met
- Only one of client secret or JWT bearer/SPIFFE authenticatio
- Only one of JWT private key or JWT bearer/SPIFFE authenticat
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/42683f6076ee8939.
Report an issue: GitHub.