quarkusio/quarkus · error · AuthenticationFailedException

DPoP proof token signature is invalid

Error message

DPoP proof token signature is invalid

What it means

Thrown when the DPoP proof JWT's signature fails verification (jose4j's JsonWebSignature.verifySignature() returns false) against the public key from the proof's own jwk header. The proof payload or header was modified after signing, or it was signed with a different key than the one declared in the header.

Source

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

                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

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

                            if (!dpopJwkThumbprint.equals(jwkProofThumbprint)) {
                                LOG.warn("DPoP access token JWK thumbprint does not match the DPoP proof JWK thumbprint");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            try {
                                JsonWebSignature jws = new JsonWebSignature();
                                jws.setAlgorithmConstraints(OidcProvider.ASYMMETRIC_ALGORITHM_CONSTRAINTS);
                                jws.setCompactSerialization((String) requestData.get(OidcUtils.DPOP_PROOF));
                                jws.setKey(publicJsonWebKey.getPublicKey());
                                if (!jws.verifySignature()) {
                                    LOG.warn("DPoP proof token signature is invalid");
                                    throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                                }
                            } catch (JoseException ex) {
                                LOG.warn("DPoP proof token signature can not be verified");
                                throw new AuthenticationFailedException(ex, invalidDPoPProofMap(request.getToken()));
                            }

                            JsonObject proofClaims = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_CLAIMS);

                            // Calculate the access token thumprint and compare with the `ath` claim

                            String accessTokenProof = proofClaims.getString(OidcConstants.DPOP_ACCESS_TOKEN_THUMBPRINT);
                            if (accessTokenProof == null) {
                                LOG.warn("DPoP proof access token hash is missing");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            String accessTokenHash = null;
                            try {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Fix the proof generator to compute the compact JWS only after all header and claim values are final.
  2. Verify the jwk header key is exactly the key used to sign the proof.
  3. Avoid proxies/interceptors that modify the DPoP header or its inputs after signing.
  4. Catch AuthenticationFailedException, regenerate the proof, and retry once with a freshly signed JWT.

Example fix

// before: mutate claims after signing
jws.setPayload(claims); jws.sign();
claims.put("iat", now); // invalidates signature

// after: finalize claims, then sign
claims.put("iat", now);
jws.setPayload(claims);
jws.sign();
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the proof signature locally before sending
JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(proof);
jws.setKey(publicKey);
if (!jws.verifySignature()) throw new IllegalStateException("Proof was not signed with the jwk header key");

Try / catch

try {
    return callWithProof();
} catch (AuthenticationFailedException e) {
    return callWithProofWithFreshlySignedJwt(); // rebuild proof, sign last
}

Prevention

When it happens

Trigger: A middleman or buggy client rewrites proof claims (iat, htu, nonce) after signing; the client signs with one key but embeds another in the jwk header; corrupted encoding of the compact serialization.

Common situations: Proxies that re-sign or re-serialize the DPoP header; home-grown proof builders that serialize claims after signing; clock-adjustment code that mutates the iat claim post-signature.

Related errors


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