{"record":{"id":"d0920c0a3eb129c6","repo":"quarkusio/quarkus","slug":"client-certificate-thumbprint-does-not-match-the-t","errorCode":null,"errorMessage":"Client certificate thumbprint does not match the token certificate thumbprint","messagePattern":"Client certificate thumbprint does not match the token certificate thumbprint","errorType":"exception","errorClass":"AuthenticationFailedException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java","lineNumber":241,"sourceCode":"                if (resolvedContext.oidcConfig().token().binding().certificate()) {\n                    result = result.onItem().transform(new Function<TokenVerificationResult, TokenVerificationResult>() {\n\n                        @Override\n                        public TokenVerificationResult apply(TokenVerificationResult t) {\n                            String tokenCertificateThumbprint = getTokenCertThumbprint(requestData, t);\n                            if (tokenCertificateThumbprint == null) {\n                                LOG.warn(\n                                        \"Access token does not contain a confirmation 'cnf' claim with the certificate thumbprint\");\n                                throw new AuthenticationFailedException(tokenMap(request.getToken()));\n                            }\n                            String clientCertificateThumbprint = (String) requestData.get(OidcConstants.X509_SHA256_THUMBPRINT);\n                            if (clientCertificateThumbprint == null) {\n                                LOG.warn(\"Client certificate thumbprint is not available\");\n                                throw new AuthenticationFailedException(tokenMap(request.getToken()));\n                            }\n                            if (!clientCertificateThumbprint.equals(tokenCertificateThumbprint)) {\n                                LOG.warn(\"Client certificate thumbprint does not match the token certificate thumbprint\");\n                                throw new AuthenticationFailedException(tokenMap(request.getToken()));\n                            }\n                            return t;\n                        }\n\n                    });\n                }\n\n                if (requestData.containsKey(OidcUtils.DPOP_PROOF_JWT_HEADERS)) {\n                    result = result.onItem().transform(new Function<TokenVerificationResult, TokenVerificationResult>() {\n\n                        @Override\n                        public TokenVerificationResult apply(TokenVerificationResult t) {\n\n                            String dpopJwkThumbprint = getDpopJwkThumbprint(requestData, t);\n                            if (dpopJwkThumbprint == null) {\n                                LOG.warn(\n                                        \"DPoP access token does not contain a confirmation 'cnf' claim with the JWK thumbprint\");\n                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java#L223-L259","documentation":"Thrown when both the access token ('cnf.x5t#S256' claim) and the TLS connection have client certificate SHA-256 thumbprints, but they differ. This means the token was issued for (bound to) a different client certificate than the one presented on the current connection, so per RFC 8705 the token must be rejected.","triggerScenarios":"A client presents certificate A on TLS but uses an access token bound to certificate B — e.g. reusing a token obtained with a different keystore entry, rotating client certificates without re-obtaining tokens, or load-balancing requests to a backend with mismatched certs.","commonSituations":"Certificate rotation: server keystore updated but cached tokens still bound to the old cert. Multiple services behind one identity sharing tokens. Copying tokens between environments (dev vs prod keystores).","solutions":["Obtain a fresh access token while authenticated with the same client certificate that is presented on the request.","If certificates were rotated, clear the token cache and re-authenticate so a new 'cnf' thumbprint is minted.","Verify the proxy/load balancer does not present its own certificate to Quarkus instead of the client's (use TLS passthrough).","Confirm the certificate used matches by comparing its SHA-256 thumbprint with the token's cnf.x5t#S256 value offline."],"exampleFix":"// before: reuse cached token after cert rotation\nString token = cachedToken; // bound to old cert\n\n// after: re-authenticate with current cert\nOAuth2TokenSelector / OidcClient client = ...;\nAccessToken token = client.authenticate(certCredential); // new cert -> new cnf claim","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    return callProtectedApi(token);\n} catch (AuthenticationFailedException e) {\n    // cert thumbprint mismatch: refresh token with the current cert\n    return callProtectedApi(reauthenticateWithCurrentCert());\n}","preventionTips":["Clear token caches after client certificate rotation","Keep the DPoP/mTLS key stable across restarts (keystore, not in-memory)","Never share cert-bound tokens between different certificates or environments","Compare cert SHA-256 thumbprint against cnf.x5t#S256 before sending"],"tags":["oidc","mtls","certificate-bound-token","thumbprint-mismatch"],"backgroundTag":"mtls-certificate-thumbprint-mismatch","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"}