quarkusio/quarkus · error · AuthenticationCompletionException
ID Token is required to contain 'exp' and 'iat' claims
Error message
ID Token is required to contain 'exp' and 'iat' claims
What it means
Quarkus initializes the OIDC session age from the ID token's 'exp' minus 'iat'. If a returned ID token lacks either claim, the session duration cannot be computed, so the code flow fails with AuthenticationCompletionException. Per OIDC spec both claims are mandatory in ID tokens.
Source
Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java:1220
}
private Uni<Void> processSuccessfulAuthentication(RoutingContext context,
TenantConfigContext configContext,
AuthorizationCodeTokens tokens,
String idToken,
SecurityIdentity securityIdentity) {
LOG.debug("ID token has been verified, removing the existing session cookie if any and creating a new one");
return removeSessionCookie(context, configContext.oidcConfig())
.chain(new Function<Void, Uni<? extends Void>>() {
@Override
public Uni<? extends Void> apply(Void t) {
JsonObject idTokenJson = OidcCommonUtils.decodeJwtContent(idToken);
if (!idTokenJson.containsKey("exp") || !idTokenJson.containsKey("iat")) {
final String error = "ID Token is required to contain 'exp' and 'iat' claims";
LOG.error(error);
throw new AuthenticationCompletionException(error);
}
long idTokenAge = idTokenJson.getLong("exp") - idTokenJson.getLong("iat");
LOG.debugf("Session age is initialized with ID token age of %d seconds", idTokenAge);
long sessionAge = idTokenAge;
if (configContext.oidcConfig().token().lifespanGrace().isPresent()) {
int lifespanGrace = configContext.oidcConfig().token().lifespanGrace().getAsInt();
LOG.debugf("Adding token lifespan grace of %d seconds to the session age", lifespanGrace);
sessionAge += lifespanGrace;
}
if (configContext.oidcConfig().token().refreshExpired()) {
if (tokens.getRefreshToken() != null) {
long sessionAgeExtension = configContext.oidcConfig().authentication().sessionAgeExtension()
.orElse(Duration.ofMinutes(5)).getSeconds();
LOG.debugf("Extending the session age with %d seconds", sessionAgeExtension);
sessionAge += sessionAgeExtension;
} else {
LOG.debug("Session age can not be extended becase a refresh token is not available");
}View on GitHub (pinned to e1c734241f)
Solutions
- Fix the OIDC provider to include mandatory 'exp' and 'iat' claims in the ID token (spec-compliant provider required).
- Decode the token payload (e.g. jwt.io) to confirm which claims are missing.
- If using a custom test issuer/stub, add exp and iat to the minted JWT.
- Upgrade the provider to a compliant version if it is known to omit claims.
Example fix
// before (test token stub)
Jwts.builder().setSubject("alice")...
// after
Jwts.builder().setSubject("alice")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 300_000))... Defensive patterns
Strategy: validation
Validate before calling
JsonObject payload = new JsonObject(Base64.getDecoder().split(idToken)[1]); // decode JWT payload
if (!payload.containsKey("exp") || !payload.containsKey("iat")) {
throw new IllegalStateException("OIDC provider issues ID tokens without exp/iat; fix provider or use a compliant one");
} Try / catch
try {
return completeLogin(tokens);
} catch (AuthenticationCompletionException e) {
if (e.getMessage() != null && e.getMessage().contains("'exp' and 'iat' claims")) {
log.error("Provider ID token is not OIDC-compliant (missing exp/iat)");
}
throw e;
} Prevention
- Validate provider ID tokens in a pre-production integration test.
- Prefer well-known compliant providers (Keycloak, Auth0, etc.).
- When writing test JWT stubs, always include iat and exp claims.
When it happens
Trigger: After a code flow token exchange, OidcCommonUtils.decodeJwtContent(idToken) produces a JsonObject where containsKey("exp") or containsKey("iat") is false.
Common situations: Custom/homegrown OIDC providers issuing non-compliant ID tokens; tokens truncated or corrupted by an intermediary; test stubs issuing hand-crafted JWTs without exp/iat; provider misconfiguration issuing opaque tokens where a JWT is expected.
Related errors
- Authorization response 'iss' parameter '%s' does not match t
- Authorization response 'iss' parameter is required but is no
- State cookie value for the %s tenant can not be encrypted: %
- Required ID token is not returned in the refresh token grant
- expected claim %s must be a list of strings
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/61503754153e9864.
Report an issue: GitHub.