Skip to content

feat: send a DPoP proof at PAR by default (RFC 9449 §10.1) - #169

Merged
osanderson merged 1 commit into
mainfrom
feat/par-dpop-binding-mode
Aug 27, 2026
Merged

feat: send a DPoP proof at PAR by default (RFC 9449 §10.1)#169
osanderson merged 1 commit into
mainfrom
feat/par-dpop-binding-mode

Conversation

@osanderson

Copy link
Copy Markdown
Collaborator

Summary

client always bound the authorization code to its DPoP key at PAR via the plain dpop_jkt parameter (RFC 9449 §10.1's Option A) and never the alternative the RFC itself recommends: presenting an actual DPoP proof at PAR (Option B) — simpler for the client (reuses the same proof-building already done at the token/resource endpoints) and, unlike dpop_jkt, real proof of possession at PAR time rather than a bare key identifier.

  • New Config.PARDPoPBinding, defaulting (zero value) to the recommended PARDPoPBindingProof. PARDPoPBindingJKT keeps today's exact behavior for interop with a deployment that has a specific reason to prefer it.
  • server needs no changes — server/par.go's reconcileParDPoPBinding already accepts both mechanisms and requires an AS supporting DPoP at PAR to (RFC 9449 §10.1's own MUST).
  • Since Option B means PAR now presents a DPoP proof, it gets the exact same nonce-challenge retry ExchangeCode already has at the token endpoint (rebuild the form — a client assertion is exactly as single-use as a DPoP proof — and resubmit once with the challenged nonce).

Test plan

  • go build ./..., go vet ./..., go test ./... all clean
  • Updated TestBeginAuthorizationCommitsDPoPKeyAtPARMatchingTokenEndpointProofJKT (now explicit about testing Option A) and new tests: the same commitment property for the default Proof mode, PAR nonce-challenge retry, and config validation for the new field (including that the zero value is accepted and behaves as the default)
  • Full local OIDF conformance run — the critical interop check, since cmd/conformance-client drives the real RP-conformance suite through BeginAuthorization unchanged: RP baseline (22/22 PASSED) and RP message-signing (28/28 PASSED) confirm the live suite's AS role accepts DPoP-header-based PAR binding. AS baseline (57 modules, 4465 conditions) and AS message-signing (71 modules, 5975 conditions) also 0 failures — no regressions anywhere.

client always committed the authorization code to its DPoP key at PAR
via the plain dpop_jkt parameter (Option A) and never the alternative
RFC 9449 §10.1 recognizes: presenting an actual DPoP proof at PAR
(Option B). §10.1 itself recommends Option B — it reuses the same
proof-building this client already does at the token and resource
endpoints, and unlike dpop_jkt, it's real proof of possession at PAR
time, not just a key identifier.

Adds Config.PARDPoPBinding, defaulting (zero value) to the recommended
PARDPoPBindingProof; PARDPoPBindingJKT keeps today's exact behavior for
interop. Every authorization server supporting DPoP at PAR must accept
both mechanisms (§10.1's own MUST) — server/par.go's
reconcileParDPoPBinding already does, unchanged by this commit.

Sending a proof at PAR means PAR can now be nonce-challenged the same
way the token endpoint already can, so PARDPoPBindingProof gets the
identical retry BeginAuthorization's ExchangeCode already has — rebuild
the form (a client assertion is exactly as single-use as a DPoP proof)
and resubmit once with the challenged nonce.

Verified against the live OIDF conformance suite: the RP suites (which
exercise this new default against a real, spec-compliant AS, not just
this module's own) pass unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@osanderson
osanderson merged commit 9cab852 into main Aug 27, 2026
8 checks passed
@osanderson
osanderson deleted the feat/par-dpop-binding-mode branch August 27, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant