{"record":{"id":"c24d90bc68608d11","repo":"quarkusio/quarkus","slug":"dpop-proof-jwk-header-is-missing","errorCode":null,"errorMessage":"DPoP proof jwk header is missing","messagePattern":"DPoP proof jwk header is missing","errorType":"exception","errorClass":"AuthenticationFailedException","httpStatus":401,"severity":"error","filePath":"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java","lineNumber":267,"sourceCode":"                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\n                            if (publicJsonWebKey.getPrivateKey() != null) {\n                                LOG.warn(\"DPoP proof JWK key is a private key but it must be a public key\");\n                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));\n                            }\n\n                            byte[] jwkProofDigest = publicJsonWebKey.calculateThumbprint(\"SHA-256\");\n                            String jwkProofThumbprint = OidcCommonUtils.base64UrlEncode(jwkProofDigest);\n","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java#L249-L285","documentation":"Thrown when the DPoP proof JWT's 'jwk' JOSE header — required by RFC 9449 to carry the public key used to sign the proof — is missing. Without it Quarkus cannot know which key must verify the proof signature, so authentication fails.","triggerScenarios":"The DPoP proof header value sent by the client is a JWT whose header lacks the 'jwk' member: hand-built proofs, proofs generated by a library with a non-standard configuration, or the 'DPoP' header containing something other than a complete proof JWT.","commonSituations":"Manually constructing the DPoP proof JSON and forgetting the jwk header; using an HTTP client interceptor that signs with 'typ':dpop+jwt but omits embedded jwk; forwarding only claims instead of the full JWT.","solutions":["Regenerate the DPoP proof ensuring the JOSE header contains {typ:'dpop+jwt', alg:'ES256', jwk:<public key JWK>}.","Upgrade or correctly configure the DPoP library so it embeds the public JWK in the proof header.","Verify the proof is a 3-part compact JWS (header.payload.signature) and the header parses as JSON with a jwk object.","Catch AuthenticationFailedException, log the proof header (without secrets), and fix the proof generator."],"exampleFix":"// before\nMap<String,Object> header = Map.of(\"typ\",\"dpop+jwt\",\"alg\",\"ES256\");\n\n// after\nMap<String,Object> header = Map.of(\n  \"typ\",\"dpop+jwt\",\n  \"alg\",\"ES256\",\n  \"jwk\", publicKeyJwk.toJson()); // RFC 9449 requires embedded public JWK","handlingStrategy":"validation","validationCode":"// Validate the proof header locally before sending\nJsonObject header = decodeJoseHeader(proof);\nif (header.getJsonObject(\"jwk\") == null) {\n    throw new IllegalArgumentException(\"DPoP proof must embed the public JWK in its jwk header\");\n}","typeGuard":"static boolean proofHasJwkHeader(String compactJws) {\n    String[] parts = compactJws.split(\"\\\\.\");\n    if (parts.length != 3) return false;\n    var h = Json.decodeValue(Base64.getUrlDecoder().decode(parts[0]));\n    return h instanceof JsonObject jo && jo.containsKey(\"jwk\");\n}","tryCatchPattern":null,"preventionTips":["Use a JOSE library to build proofs; never hand-roll the header","Header must contain typ=dpop+jwt, alg, and jwk","Unit-test proof generation against PublicJsonWebKey.Factory parsing","Log (redacted) proof headers when debugging DPoP 401s"],"tags":["oidc","dpop","jose-header","proof-validation"],"backgroundTag":"dpop-proof-missing-jwk-header","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"}