Add the RFC 9449 ath claim to DPoP proofs - #786
Open
JamesRadley wants to merge 1 commit into
Open
JamesRadley wants to merge 1 commit into
JamesRadley wants to merge 1 commit into
Conversation
RFC 9449 section 4.3 requires a DPoP proof to carry an `ath` claim — the
base64url encoding of the SHA-256 hash of the ASCII encoding of the access
token — whenever the proof accompanies an access token. The harness omitted it,
so a resource server that enforces the requirement rejects every authenticated
request the harness makes.
Both proof sites that accompany a token are fixed, not just the obvious one:
- `getAuthHeaders` already had the token to hand.
- `authorize` sets the Authorization header itself and then delegates to
`signRequest`, which builds the proof from a provisional request and so had
no way to know the token. `signRequest` gains an overload taking the token;
the existing single-argument signature is unchanged and still means "no
access token accompanies this request".
The token request in `requestAccessToken` correctly continues to send a proof
with no `ath`: there is no access token at that point.
The claim names are lifted into HttpConstants alongside the existing DPoP
constants.
josepharuja
added a commit
to josepharuja/conformance-test-harness
that referenced
this pull request
Aug 28, 2026
RFC 9449 §4.3 step 12 requires a DPoP proof presented together with an access token to carry ath — the base64url-encoded SHA-256 of the token's ASCII value (§4.2) — and requires resource servers to verify it. Client.generateDpopToken sets jti, htm, htu and iat; this adds ath, guarded so proofs made before a token exists (the token request itself) are unchanged. JwsUtils.accessTokenHash carries the hashing, tested against the worked example in RFC 9449 §4.3. ClientTest verifies the claim is present on getAuthHeaders' proofs and absent from token-less signRequest proofs. Fixes solid-contrib#767. Fixes solid-contrib#786. Signed-off-by: Joseph Antony Aruja <joseph.a.aruja@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC 9449 §4.3 requires a DPoP proof to carry an
athclaim —base64url(SHA-256(ASCII(access_token)))— whenever the proof accompanies an access token. It is what binds a proof to one specific token; without it a proof captured from one request can be presented alongside a different token issued to the same key.The harness currently sets only
jti,htm,htuandiat. Against a resource server that enforces the requirement, every authenticated request the harness makes is rejected before any test assertion runs, so the whole suite is unusable against that server. We hit this running the harness against our own Solid server, which enforcesath; the requests failed with401 invalid_dpop_proof, and adding the claim was the only change needed to get the suite running.What changed
Both proof sites that accompany an access token now bind to it:
getAuthHeadersalready had the token to hand.authorizesets theAuthorizationheader itself and then delegates tosignRequest, which builds the proof from a provisional request and so had no way to know the token.signRequestgains an overload that takes it. The existing single-argument signature is unchanged and still means "no access token accompanies this request", so this is source-compatible for any caller (including Karate features) using the public method.requestAccessTokendeliberately continues to send a proof with noath: there is no access token at that point, and RFC 9449 only requires the claim when one is present.The three claim names are lifted into
HttpConstantsalongside the existing DPoP constants.Tests
Three cases added to
ClientTest:getAuthHeadersDpopBindsProofToAccessToken— the proof'sathmatches the token in theAuthorizationheader.signRequestWithAccessTokenBindsProofToIt— the overload setsathcorrectly.signRequestWithoutAccessTokenOmitsAth— the no-token path still omitsath, andhtm/htuare unchanged.ClientTestpasses in full (60 tests).Happy to adjust naming or approach if you would prefer the token threaded differently.