{"record":{"id":"53508f7fb2e3fa1b","repo":"quarkusio/quarkus","slug":"expired","errorCode":"EXPIRED","errorMessage":"Logout token issued to client %s expired %d seconds ago","messagePattern":"Logout token issued to client (.+?) expired (.+?) seconds ago","errorType":"error_code","errorClass":"InvalidJwtException","httpStatus":401,"severity":"warning","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java","lineNumber":210,"sourceCode":"                asymmetricKeyResolver, enforceExpReq, oidcConfig.token().issuedAtRequired());\n    }\n\n    public TokenVerificationResult verifyLogoutJwtToken(String token) throws InvalidJwtException {\n        final boolean enforceExpReq = !oidcConfig.token().age().isPresent();\n        TokenVerificationResult result = verifyJwtTokenInternal(token, true, false, null, ASYMMETRIC_ALGORITHM_CONSTRAINTS,\n                asymmetricKeyResolver, enforceExpReq, oidcConfig.token().issuedAtRequired());\n        if (!enforceExpReq) {\n            // Expiry check was skipped during the initial verification but if the logout token contains the exp claim\n            // then it must be verified\n            final Long exp = result.localVerificationResult().getLong(Claims.exp.name());\n            if (exp != null) {\n                final long secondsAfterExpiry = now() / 1000 - (exp + getLifespanGrace());\n                if (secondsAfterExpiry > 0) {\n                    String error = \"Logout token issued to client %s expired %d seconds ago\".formatted(\n                            oidcConfig.clientId().get(),\n                            secondsAfterExpiry);\n                    LOG.warn(error);\n                    throw new InvalidJwtException(error, List.of(new ErrorCodeValidator.Error(ErrorCodes.EXPIRED, error)),\n                            null);\n                }\n            }\n        }\n        return result;\n    }\n\n    private TokenVerificationResult verifyJwtTokenInternal(String token,\n            boolean enforceAudienceVerification,\n            boolean subjectRequired,\n            String nonce,\n            AlgorithmConstraints algConstraints,\n            VerificationKeyResolver verificationKeyResolver, boolean enforceExpReq, boolean issuedAtRequired)\n            throws InvalidJwtException {\n        JwtConsumerBuilder builder = new JwtConsumerBuilder();\n\n        builder.setVerificationKeyResolver(verificationKeyResolver);\n","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java#L192-L228","documentation":"Back-channel logout tokens (JWTs sent by the OIDC provider on logout) are validated including expiration. This InvalidJwtException with ErrorCodes.EXPIRED is thrown when the logout token's 'exp' claim plus the configured lifespan grace has passed, reporting how many seconds ago it expired.","triggerScenarios":"verifyLogoutJwtToken receives a logout token whose exp < now - getLifespanGrace(); often caused by provider retries of old logout events or large clock skew between IdP and app.","commonSituations":"Keycloak admin revoking sessions causing replay of stale logout tokens; app downtime delaying processing of queued logout tokens; clock drift between containers.","solutions":["Increase quarkus.oidc.token.lifespan-grace to tolerate the observed delay/skew.","Synchronize clocks (NTP) between the IdP and the Quarkus application hosts.","If the provider retries stale logout tokens, ensure the app acknowledges/flushes the logout queue promptly.","Verify with the provider why logout tokens are generated so far in the past."],"exampleFix":"// before\n# (default lifespan grace)\n// after\nquarkus.oidc.token.lifespan-grace=60","handlingStrategy":"try-catch","validationCode":"Long exp = logoutToken.getLongClaimValue(\"exp\");\nif (exp != null && System.currentTimeMillis() / 1000 > exp + graceSeconds) {\n    // skip processing; token already expired\n}","typeGuard":"boolean isLogoutTokenFresh(Claims claims, long grace) {\n    return claims.getExpirationTime() == null\n        || System.currentTimeMillis() / 1000 <= claims.getExpirationTime() + grace;\n}","tryCatchPattern":"try {\n    provider.verifyLogoutJwtToken(token);\n} catch (InvalidJwtException e) {\n    if (e.hasError(ErrorCodes.EXPIRED)) { LOG.info(\"Stale logout token ignored\"); return; }\n    throw e;\n}","preventionTips":["Set quarkus.oidc.token.lifespan-grace to cover processing delays and retries","Run NTP time sync on both IdP and application hosts","Process logout callbacks promptly so queued tokens don't age past exp"],"tags":["oidc","logout-token","jwt-expired","clock-skew"],"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-14T00:17:10.932Z"}