quarkusio/quarkus · error · AuthenticationFailedException

DPoP proof jwk header is missing

Error message

DPoP proof jwk header is missing

What it means

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.

Source

Thrown at extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java:267

                if (requestData.containsKey(OidcUtils.DPOP_PROOF_JWT_HEADERS)) {
                    result = result.onItem().transform(new Function<TokenVerificationResult, TokenVerificationResult>() {

                        @Override
                        public TokenVerificationResult apply(TokenVerificationResult t) {

                            String dpopJwkThumbprint = getDpopJwkThumbprint(requestData, t);
                            if (dpopJwkThumbprint == null) {
                                LOG.warn(
                                        "DPoP access token does not contain a confirmation 'cnf' claim with the JWK thumbprint");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            JsonObject proofHeaders = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_HEADERS);

                            JsonObject jwkProof = proofHeaders.getJsonObject(OidcConstants.DPOP_JWK_HEADER);
                            if (jwkProof == null) {
                                LOG.warn("DPoP proof jwk header is missing");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            PublicJsonWebKey publicJsonWebKey = null;
                            try {
                                publicJsonWebKey = PublicJsonWebKey.Factory.newPublicJwk(jwkProof.getMap());
                            } catch (JoseException ex) {
                                LOG.warn("DPoP proof jwk header does not represent a valid JWK key");
                                throw new AuthenticationFailedException(ex, invalidDPoPProofMap(request.getToken()));
                            }

                            if (publicJsonWebKey.getPrivateKey() != null) {
                                LOG.warn("DPoP proof JWK key is a private key but it must be a public key");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            byte[] jwkProofDigest = publicJsonWebKey.calculateThumbprint("SHA-256");
                            String jwkProofThumbprint = OidcCommonUtils.base64UrlEncode(jwkProofDigest);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Regenerate the DPoP proof ensuring the JOSE header contains {typ:'dpop+jwt', alg:'ES256', jwk:<public key JWK>}.
  2. Upgrade or correctly configure the DPoP library so it embeds the public JWK in the proof header.
  3. Verify the proof is a 3-part compact JWS (header.payload.signature) and the header parses as JSON with a jwk object.
  4. Catch AuthenticationFailedException, log the proof header (without secrets), and fix the proof generator.

Example fix

// before
Map<String,Object> header = Map.of("typ","dpop+jwt","alg","ES256");

// after
Map<String,Object> header = Map.of(
  "typ","dpop+jwt",
  "alg","ES256",
  "jwk", publicKeyJwk.toJson()); // RFC 9449 requires embedded public JWK
Defensive patterns

Strategy: validation

Validate before calling

// Validate the proof header locally before sending
JsonObject header = decodeJoseHeader(proof);
if (header.getJsonObject("jwk") == null) {
    throw new IllegalArgumentException("DPoP proof must embed the public JWK in its jwk header");
}

Type guard

static boolean proofHasJwkHeader(String compactJws) {
    String[] parts = compactJws.split("\\.");
    if (parts.length != 3) return false;
    var h = Json.decodeValue(Base64.getUrlDecoder().decode(parts[0]));
    return h instanceof JsonObject jo && jo.containsKey("jwk");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/c24d90bc68608d11. Report an issue: GitHub.