{"record":{"id":"699c2f014fad053d","repo":"quarkusio/quarkus","slug":"dpop-access-token-does-not-contain-a-confirmation","errorCode":null,"errorMessage":"DPoP access token does not contain a confirmation 'cnf' claim with the JWK thumbprint","messagePattern":"DPoP access token does not contain a confirmation 'cnf' claim with the JWK thumbprint","errorType":"exception","errorClass":"AuthenticationFailedException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java","lineNumber":259,"sourceCode":"                                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()));\n                            }\n\n                            JsonObject proofHeaders = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_HEADERS);\n\n                            JsonObject jwkProof = proofHeaders.getJsonObject(OidcConstants.DPOP_JWK_HEADER);\n                            if (jwkProof == null) {\n                                LOG.warn(\"DPoP proof jwk header is missing\");\n                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            PublicJsonWebKey publicJsonWebKey = null;\n                            try {\n                                publicJsonWebKey = PublicJsonWebKey.Factory.newPublicJwk(jwkProof.getMap());\n                            } catch (JoseException ex) {\n                                LOG.warn(\"DPoP proof jwk header does not represent a valid JWK key\");\n                                throw new AuthenticationFailedException(ex, invalidDPoPProofMap(request.getToken()));\n                            }\n","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java#L241-L277","documentation":"Thrown during DPoP (RFC 9449) proof-of-possession verification: the access token must contain a 'cnf' claim with a 'jkt' (JWK thumbprint) value binding it to the sender's DPoP key, but no such claim exists. Quarkus then rejects the DPoP-bound request because the token cannot be tied to the proof's public key.","triggerScenarios":"A request includes a DPoP proof header, so the provider runs DPoP verification, but the access token was issued without DPoP binding (no cnf.jkt) — e.g. the token was obtained via a plain client_credentials/token call instead of one that sends the DPoP proof JWT to the token endpoint.","commonSituations":"Mixing a normal (non-DPoP) access token with a DPoP proof header added by an HTTP client library that enables DPoP globally; authorization server configured to issue DPoP tokens only for some clients; token obtained before enabling DPoP on the client.","solutions":["Obtain the access token by sending a DPoP proof JWT with the token request so the AS embeds cnf.jkt (quarkus.oidc-client with DPoP enabled, or add the DPoP header to the token call).","Ensure quarkus.oidc.token.verify-access-token-with-user-info / DPoP settings are consistent: if DPoP proofs are sent, tokens must be DPoP-bound by the AS.","Disable DPoP proof generation on the HTTP client if the token is not DPoP-bound.","Catch AuthenticationFailedException and re-authenticate to obtain a DPoP-bound token."],"exampleFix":"// before: plain token + DPoP proof header\nclient.post(\"/protected\").putHeader(\"DPoP\", proof(token));\n\n// after: request DPoP-bound token\n// token request also carries a DPoP proof (htiu=token endpoint) so cnf.jkt is issued\nclient.post(\"/token\").putHeader(\"DPoP\", proofForTokenEndpoint(...));","handlingStrategy":"validation","validationCode":"// Check the token is DPoP-bound before sending a DPoP proof\nJsonWebToken jwt = parseUnverified(token);\nvar cnf = jwt.getClaimValue(\"cnf\", Map.class);\nif (cnf == null || cnf.get(\"jkt\") == null) {\n    token = obtainDpopBoundToken(); // include a DPoP proof in the token request\n}","typeGuard":"static boolean isDpopBoundToken(JsonWebToken t) {\n    Map<String,Object> cnf = t.getClaimValue(\"cnf\", Map.class);\n    return cnf != null && cnf.containsKey(\"jkt\");\n}","tryCatchPattern":"try {\n    return callWithDpop(token);\n} catch (AuthenticationFailedException e) {\n    return callWithDpop(refreshDpopBoundToken());\n}","preventionTips":["Always request tokens with a DPoP proof when using DPoP at the resource server","Don't enable DPoP proof headers globally for non-DPoP tokens","Re-authenticate after changing client DPoP configuration","Inspect cnf.jkt presence when debugging DPoP failures"],"tags":["oidc","dpop","proof-of-possession","authentication"],"backgroundTag":"dpop-token-missing-jkt-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"}