{"record":{"id":"42683f6076ee8939","repo":"quarkusio/quarkus","slug":"issued-at-invalid-past","errorCode":"ISSUED_AT_INVALID_PAST","errorMessage":"Token age exceeds the configured token age property","messagePattern":"Token age exceeds the configured token age property","errorType":"error_code","errorClass":"InvalidJwtException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java","lineNumber":342,"sourceCode":"                String newHeaders = new String(\n                        Base64.getUrlEncoder().withoutPadding().encode(headers.toString().getBytes()),\n                        StandardCharsets.UTF_8);\n                int dotIndex = token.indexOf('.');\n                String newToken = newHeaders + token.substring(dotIndex);\n                return newToken;\n            }\n        }\n        return token;\n    }\n\n    private void verifyTokenAge(Long iat) throws InvalidJwtException {\n        if (oidcConfig.token().age().isPresent() && iat != null) {\n            final long now = now() / 1000;\n\n            if (now - iat > oidcConfig.token().age().get().toSeconds() + getLifespanGrace()) {\n                final String errorMessage = \"Token age exceeds the configured token age property\";\n                LOG.warn(errorMessage);\n                throw new InvalidJwtException(errorMessage,\n                        List.of(new ErrorCodeValidator.Error(ErrorCodes.ISSUED_AT_INVALID_PAST, errorMessage)), null);\n            }\n        }\n    }\n\n    public Uni<TokenVerificationResult> refreshJwksAndVerifyJwtToken(String token, boolean enforceAudienceVerification,\n            boolean subjectRequired, String nonce) {\n        return asymmetricKeyResolver.refresh().onItem()\n                .transformToUni(new Function<Void, Uni<? extends TokenVerificationResult>>() {\n\n                    @Override\n                    public Uni<? extends TokenVerificationResult> apply(Void v) {\n                        try {\n                            return Uni.createFrom()\n                                    .item(verifyJwtToken(token, enforceAudienceVerification, subjectRequired, nonce));\n                        } catch (Throwable t) {\n                            return Uni.createFrom().failure(t);\n                        }","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java#L324-L360","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nquarkus.oidc.token.age=1M\n// after\nquarkus.oidc.token.age=1H","handlingStrategy":"validation","validationCode":"Long iat = jwt.getClaims().getIssuedAt();\nlong maxAgeSec = config.token().age().get().toSeconds();\nif (iat != null && System.currentTimeMillis() / 1000 - iat > maxAgeSec + grace) {\n    // reject before calling the provider\n}","typeGuard":"boolean isWithinConfiguredAge(Claims claims, Duration maxAge, long grace) {\n    return claims.getIssuedAt() == null\n        || System.currentTimeMillis() / 1000 - claims.getIssuedAt() <= maxAge.toSeconds() + grace;\n}","tryCatchPattern":"try {\n    return provider.verifyJwtToken(token, enforceAudience);\n} catch (InvalidJwtException e) {\n    if (e.hasError(ErrorCodes.ISSUED_AT_INVALID_PAST)) { /* refresh token */ }\n    throw e;\n}","preventionTips":["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"],"tags":["oidc","jwt","token-age","configuration"],"backgroundTag":"jwt-token-expired","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"}