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

  1. Fix the OIDC provider to include mandatory 'exp' and 'iat' claims in the ID token (spec-compliant provider required).
  2. Decode the token payload (e.g. jwt.io) to confirm which claims are missing.
  3. If using a custom test issuer/stub, add exp and iat to the minted JWT.
  4. 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

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


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/61503754153e9864. Report an issue: GitHub.