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
- Add 'ath' to the proof claims: base64urlEncode(SHA-256 bytes of the ASCII access token) on every protected-resource DPoP proof.
- 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).
- Regenerate a new proof per request — never reuse proofs, which also fails replay checks.
- 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
- Design proof builders to always accept the access token and compute ath
- Remember ath is required for resource requests (not for the token endpoint call)
- Regenerate the proof whenever the access token changes
- Unit-test that ath equals base64url(SHA-256(token)) exactly, with no padding
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
- DPoP proof jwk header is missing
- DPoP access token does not contain a confirmation 'cnf' clai
- DPoP proof jwk header does not represent a valid JWK key
- DPoP proof JWK key is a private key but it must be a public
- DPoP access token JWK thumbprint does not match the DPoP pro
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/ce6a08599954c6f3.
Report an issue: GitHub.