feat(sdk): DSPX-3848 add DPoP client support with HTTP RoundTripper - #3581
feat(sdk): DSPX-3848 add DPoP client support with HTTP RoundTripper#3581dmihalcik-virtru wants to merge 2 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds DPoP configuration, transport, and SDK/client wiring for token-bound requests, plus CLI flag plumbing and host-origin normalization in service auth. ChangesDPoP Support
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request implements RFC 9449 DPoP (Demonstrating Proof-of-Possession) client support for the OpenTDF Go SDK. By introducing a custom HTTP RoundTripper, the SDK can now generate and attach DPoP proofs to HTTP requests, handle server-side nonce challenges, and perform URI normalization. This work is a key component of the broader Keycloak v26 upgrade, ensuring secure, proof-of-possession-based authentication for HTTP-based interactions within the platform. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. The proof is shown in token light, With DPoP we do it right. No replay here, the nonce is set, A secure path for the internet. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces RFC 9449 DPoP (Demonstrating Proof-of-Possession) support to the SDK by adding a new DPoPTransport and integrating it into the client setup. The code review identified several critical and high-severity issues in the transport implementation, including a potential bug where request bodies are consumed and not reset on retry, concurrency data races on shared fields like t.Base and t.nonceCache, and the bypass of custom transport configurations when retrieving access tokens. Additionally, optimizations were suggested to cache parsed token endpoint URLs and normalize URL origins to lowercase to prevent cache misses.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
441af7b to
61316ef
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
ebc3e40 to
37ed377
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
37ed377 to
b9dd8d8
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
5c4f57a to
4cec258
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
7f26f61 to
6413b45
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Follow-up to review on #3581: - document that DPoPTransport.TokenEndpoint must not be mutated after first use (the parsed URL is cached) - exercise both RSA and ES256 in the primary DPoP transport tests; generateTestKey now returns the production-default ES256/P-256 key - document the SupportedFeatures stability contract for the xtest harness - use strings.Builder in removeDotSegments to avoid O(n^2) accumulation Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
6413b45 to
dc8cc81
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Adds Demonstrating Proof-of-Possession (DPoP) support to the Go SDK and platform auth: - SDK DPoP client via an HTTP RoundTripper, with nonce-400 retry and request-body buffering for replay - SigningAlgorithm type and WithDPoPAlgorithm option; centralized DPoP algorithm validation and single-owner headers - Robust DPoP origin/HTU normalization (IPv6 literals, default ports) - Move DPoP enforcement into dpop.enforce service config - Expose supported features via `otdfctl --version --json`
Follow-up to review on #3581: - document that DPoPTransport.TokenEndpoint must not be mutated after first use (the parsed URL is cached) - exercise both RSA and ES256 in the primary DPoP transport tests; generateTestKey now returns the production-default ES256/P-256 key - document the SupportedFeatures stability contract for the xtest harness - use strings.Builder in removeDotSegments to avoid O(n^2) accumulation Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
dc8cc81 to
b38bd4a
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
Follow-up to review on #3581: - honor the token source's declared scheme in DPoPTransport: a bearer token source (AccessTokenCredentialSource reporting a non-DPoP scheme) now gets Authorization: Bearer with no proof, matching the credential interceptor instead of forcing every resource request onto DPoP - document that DPoPTransport.TokenEndpoint must not be mutated after first use (the parsed URL is cached) - exercise both RSA and ES256 in the primary DPoP transport tests; generateTestKey now returns the production-default ES256/P-256 key - document the SupportedFeatures stability contract for the xtest harness - use strings.Builder in removeDotSegments to avoid O(n^2) accumulation Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
b38bd4a to
ecee503
Compare
Benchmark results, click to expandBenchmark authorization.GetDecisions Results:
Benchmark authorization.v2.GetMultiResourceDecision Results:
Benchmark Statistics
Bulk Benchmark Results
TDF3 Benchmark Results:
|
|
Summary
Adds RFC 9449 DPoP (Demonstrating Proof-of-Possession) support to the Go SDK, so HTTP/ConnectRPC calls carry a DPoP proof and can use sender-constrained tokens. Part of the Keycloak v26 / DPoP effort tracked in DSPX-62.
What changed
SDK
DPoPTransport(http.RoundTripper) that adds a DPoP proof to every request — token endpoint and resource calls alike. Composes with anyhttp.Client.DPoP-Noncechallenge (RFC 9449 §8): on a401carrying a nonce, cache it, re-sign, and retry once; refresh the cached nonce from2xxresponses. Nonce cache is per-origin and thread-safe.htu(lowercase scheme/host, strip default ports, drop query/fragment, preserve escaped path bytes).WithDPoPKeyPEM/WithDPoPAlgorithm/ JWK; defaults to an ephemeral EC P-256 (ES256) key when none is supplied.SupportedFeatures()reports["dpop", "dpop_nonce_challenge", "connectrpc"]so the xtest harness can probe capabilities.otdfctl
--version --jsonnow includessupported_features.service
htuvalidation.Testing
go test ./...andcd sdk && go test -run TestREADMECodeBlockspass; lint clean.opentdf/testsxtest: 🟢 https://github.com/opentdf/tests/actions/runs/29624686479Jira: DSPX-3397