Add RFC 9449 DPoP ath claim to protected-resource proofs#767
Open
jeswr wants to merge 1 commit into
Open
Conversation
When a DPoP proof accompanies an access token to a protected resource, RFC 9449 §4.2/§7.1 REQUIRES the proof to carry an `ath` claim (base64url SHA-256 of the access token) so the proof is cryptographically bound to that specific token, and the resource server MUST verify it. `Client.generateDpopToken` only set `htm`/`htu`/`jti`/`iat`, so the harness could not authenticate against any resource server that enforces `ath` (e.g. one built on panva's oauth4webapi) — the server correctly rejects a proof with no `ath`. Thread the access token into `generateDpopToken` and set `ath` when present. The public `signRequest` (resource requests) binds the current access token; the token-endpoint request signs with an explicit `null` so its proof never carries `ath` — including during a refresh, where a stale token is still held in the field. Add tests asserting the resource-request proof from both `getAuthHeaders` and `signRequest` carries `ath` = base64url(SHA-256(access token)). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the HTTP client’s DPoP proof generation to comply with RFC 9449 by binding protected-resource proofs to the access token via the required ath claim, and adds unit tests to validate the behavior.
Changes:
- Add
ath(base64url(SHA-256(access token))) to DPoP proofs when an access token is presented to a protected resource. - Ensure token-endpoint DPoP proofs do not include
athby explicitly bindingnullduring token requests/refresh. - Add tests that decode the DPoP JWT payload and assert
athis present and correctly computed for resource requests.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/main/java/org/solid/testharness/http/Client.java |
Thread access token binding into DPoP proof generation and add ath computation helper. |
src/main/java/org/solid/testharness/http/HttpConstants.java |
Introduce a constant for the DPoP ath claim key. |
src/test/java/org/solid/testharness/http/ClientTest.java |
Add tests to assert protected-resource DPoP proofs include the correct ath claim. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+381
to
+384
| // it via `ath`. | ||
| return signRequest(builder, accessToken); | ||
| } | ||
|
|
Comment on lines
+505
to
+510
| final Map<String, String> headers = client.getAuthHeaders("GET", TEST_URL); | ||
| final var claims = dpopProofClaims(headers.get(HttpConstants.HEADER_DPOP)); | ||
| final var expected = Base64.getUrlEncoder().withoutPadding().encodeToString( | ||
| MessageDigest.getInstance("SHA-256").digest(accessToken.getBytes(StandardCharsets.US_ASCII))); | ||
| assertEquals(expected, claims.get("ath")); | ||
| } |
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.
Problem
When a DPoP proof accompanies an access token in protected-resource access, RFC 9449 §4.2/§7.1 requires the proof to carry an
athclaim — the base64url-encoded SHA-256 of the access token — so the proof is cryptographically bound to that specific token, and the resource server MUST verify it. (The same requirement has been indraft-ietf-oauth-dpopsince draft-03/04, the version Solid-OIDC references.)Client.generateDpopTokencurrently sets onlyhtm/htu/jti/iat, so the proofs it sends on resource requests have noath. As a result the harness cannot authenticate against a resource server that enforcesath— the server (correctly) rejects the proof. I hit this validating a Solid server whose resource-server token+DPoP validation is built on panva'soauth4webapi, which enforcesathunconditionally for DPoP: the harness authenticated, discovered the pod, then got401onHEAD /<pod>/with "JWT ath (access token hash) claim missing", aborting in PREPARE SERVER before any scenario ran.Change
generateDpopTokenand setath = base64url(SHA-256(token))when a token is present.signRequest(resource requests) binds the current access token; the token-endpoint request signs with an explicitnull, so its proof never carriesath— including during a token refresh, where a stale token is still held in the field.getAuthHeadersandsignRequestcarriesath= base64url(SHA-256(access token)).No behaviour change for servers that don't enforce
ath; this only adds the spec-required claim.🤖 Generated with Claude Code