{"record":{"id":"61503754153e9864","repo":"quarkusio/quarkus","slug":"id-token-is-required-to-contain-exp-and-iat-cl","errorCode":null,"errorMessage":"ID Token is required to contain 'exp' and 'iat' claims","messagePattern":"ID Token is required to contain 'exp' and 'iat' claims","errorType":"http","errorClass":"AuthenticationCompletionException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java","lineNumber":1220,"sourceCode":"    }\n\n    private Uni<Void> processSuccessfulAuthentication(RoutingContext context,\n            TenantConfigContext configContext,\n            AuthorizationCodeTokens tokens,\n            String idToken,\n            SecurityIdentity securityIdentity) {\n        LOG.debug(\"ID token has been verified, removing the existing session cookie if any and creating a new one\");\n        return removeSessionCookie(context, configContext.oidcConfig())\n                .chain(new Function<Void, Uni<? extends Void>>() {\n\n                    @Override\n                    public Uni<? extends Void> apply(Void t) {\n                        JsonObject idTokenJson = OidcCommonUtils.decodeJwtContent(idToken);\n\n                        if (!idTokenJson.containsKey(\"exp\") || !idTokenJson.containsKey(\"iat\")) {\n                            final String error = \"ID Token is required to contain 'exp' and 'iat' claims\";\n                            LOG.error(error);\n                            throw new AuthenticationCompletionException(error);\n                        }\n                        long idTokenAge = idTokenJson.getLong(\"exp\") - idTokenJson.getLong(\"iat\");\n                        LOG.debugf(\"Session age is initialized with ID token age of %d seconds\", idTokenAge);\n                        long sessionAge = idTokenAge;\n                        if (configContext.oidcConfig().token().lifespanGrace().isPresent()) {\n                            int lifespanGrace = configContext.oidcConfig().token().lifespanGrace().getAsInt();\n                            LOG.debugf(\"Adding token lifespan grace of %d seconds to the session age\", lifespanGrace);\n                            sessionAge += lifespanGrace;\n                        }\n                        if (configContext.oidcConfig().token().refreshExpired()) {\n                            if (tokens.getRefreshToken() != null) {\n                                long sessionAgeExtension = configContext.oidcConfig().authentication().sessionAgeExtension()\n                                        .orElse(Duration.ofMinutes(5)).getSeconds();\n                                LOG.debugf(\"Extending the session age with %d seconds\", sessionAgeExtension);\n                                sessionAge += sessionAgeExtension;\n                            } else {\n                                LOG.debug(\"Session age can not be extended becase a refresh token is not available\");\n                            }","sourceCodeStart":1202,"sourceCodeEnd":1238,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java#L1202-L1238","documentation":"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.","triggerScenarios":"After a code flow token exchange, OidcCommonUtils.decodeJwtContent(idToken) produces a JsonObject where containsKey(\"exp\") or containsKey(\"iat\") is false.","commonSituations":"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.","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."],"exampleFix":"// before (test token stub)\nJwts.builder().setSubject(\"alice\")...\n// after\nJwts.builder().setSubject(\"alice\")\n    .setIssuedAt(new Date())\n    .setExpiration(new Date(System.currentTimeMillis() + 300_000))...","handlingStrategy":"validation","validationCode":"JsonObject payload = new JsonObject(Base64.getDecoder().split(idToken)[1]); // decode JWT payload\nif (!payload.containsKey(\"exp\") || !payload.containsKey(\"iat\")) {\n    throw new IllegalStateException(\"OIDC provider issues ID tokens without exp/iat; fix provider or use a compliant one\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    return completeLogin(tokens);\n} catch (AuthenticationCompletionException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"'exp' and 'iat' claims\")) {\n        log.error(\"Provider ID token is not OIDC-compliant (missing exp/iat)\");\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["oidc","id-token","jwt-claims","code-flow"],"backgroundTag":"missing-required-jwt-claim","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}