quarkusio/quarkus · error · AuthenticationFailedException

DPoP proof access token hash is missing

Error message

DPoP proof access token hash is missing

What it means

Thrown when the verified DPoP proof lacks the 'ath' (access token hash) claim, required by RFC 9449 whenever the proof accompanies a resource request carrying an access token. Without 'ath', the proof cannot be cryptographically tied to this specific access token, so Quarkus rejects the request.

Source

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

                                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 {
                                accessTokenHash = OidcCommonUtils.base64UrlEncode(
                                        OidcUtils.getSha256Digest(request.getToken().getToken()));
                            } catch (NoSuchAlgorithmException ex) {
                                // SHA256 is always supported
                            }

                            if (!accessTokenProof.equals(accessTokenHash)) {
                                LOG.warn("DPoP access token hash does not match the DPoP proof access token hash");
                                throw new AuthenticationFailedException(invalidDPoPProofMap(request.getToken()));
                            }

                            return t;
                        }

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add 'ath' to the proof claims: base64urlEncode(SHA-256 bytes of the ASCII access token) on every protected-resource DPoP proof.
  2. Make the proof factory accept the access token so 'ath' is always computed at request time (unlike jti/iat, ath must reflect the current token).
  3. Regenerate a new proof per request — never reuse proofs, which also fails replay checks.
  4. Catch AuthenticationFailedException and rebuild the proof including 'ath', then retry.

Example fix

// before
claims.put("htm", "GET"); claims.put("htu", url); // no ath

// after
claims.put("htm", "GET"); claims.put("htu", url);
claims.put("ath", Base64.getUrlEncoder().withoutPadding()
    .encodeToString(MessageDigest.getInstance("SHA-256").digest(accessToken.getBytes(UTF_8))));
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every resource-request proof carries ath
if (!proofClaims.containsKey("ath")) {
    byte[] digest = MessageDigest.getInstance("SHA-256")
        .digest(accessToken.getBytes(StandardCharsets.US_ASCII));
    proofClaims.put("ath", Base64.getUrlEncoder().withoutPadding().encodeToString(digest));
}

Type guard

static boolean proofHasAth(Map<String,Object> claims) {
    return claims.containsKey("ath") && claims.get("ath") instanceof String s && !s.isBlank();
}

Try / catch

try {
    return callWithProof(proof);
} catch (AuthenticationFailedException e) {
    return callWithProof(rebuildProofWithAth(accessToken));
}

Prevention

When it happens

Trigger: The client builds the DPoP proof for a protected-resource request without computing 'ath' = base64url(SHA-256(accessToken)) and adding it to the proof claims; proof-generation code intended only for the token endpoint is reused for API calls.

Common situations: Older/naive DPoP implementations that only set htm/htu/iat/jti; frameworks where the proof builder has no access to the access token at proof time; tokens added to requests after the proof was generated.

Related errors


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