feat: DPoP nonce-challenge support for the resource server - #166
Merged
Conversation
Foundational pieces for DPoP nonce-challenge support (RFC 9449 §8, §9): a single-use NonceStore contract (storage package) plus its in-memory reference implementation, mirroring SessionStore/ReplayStore's own issue-once/consume-once shape exactly. internal/dpop.VerifiedProof now also surfaces the proof's own "nonce" claim unconditionally — unlike RequiredNonce's compare-to-one-known-value check, a caller implementing single-use, issued-per-challenge nonces doesn't know the expected value in advance and has to look up whether the presented one was actually issued, which only that caller can do. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neither this module's resource role nor its server role has ever implemented the resource-server half of RFC 9449 §8/§9's nonce challenge — only client consumes one, from a third-party AS. Adds it to resource.Verifier as genuinely optional (Dependencies.Nonces nil disables it entirely, same as today): a request whose DPoP proof carries no current, unconsumed nonce is rejected with the new ErrorUseDPoPNonce code and a freshly issued replacement attached (via Error.Nonce); a successful Verify proactively issues another one (AuthorizationContext.NextDPoPNonce) so steady-state traffic only pays the extra round trip once, at the very first request. Deliberately not a required-with-visible-opt-out dependency like Revocation: RFC 9449 §8 makes this a resource-server MAY, and declining it is the normal, fully spec-compliant default, not a security check this module considers non-negotiable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New -dpop-nonce-challenge flag (off by default) demonstrates resource.Verifier's new nonce-challenge support end to end on the /accounts and /userinfo example endpoints: DPoP-Nonce is set from Error.Nonce on a use_dpop_nonce rejection and from AuthorizationContext.NextDPoPNonce on success. Off by default because the OIDF suite's own AS-plan protected-resource caller isn't guaranteed to implement the client-side retry the way this module's own client package does — flipping it on unconditionally would risk breaking unrelated AS conformance. TestSmokeUserInfoWithDPoPNonceChallenge confirms the two independently built halves actually interoperate: FetchUserInfo succeeds transparently against a nonce-challenging endpoint, entirely through client's existing ResourceClient.Do retry logic, with no special handling in the test itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
This was referenced Aug 27, 2026
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.


Summary
Adds resource-server DPoP nonce-challenge support (RFC 9449 §8/§9), which no role in this module implemented before — only
clientconsumed one, from a third-party AS. Scoped to theresourcepackage (used to protect UserInfo or any other resource endpoint an AS hosts), notserver's token endpoint.internal/dpop:VerifiedProofnow surfaces the proof's ownnonceclaim unconditionally (needed since single-use, issued-per-challenge nonces can't useRequiredNonce's compare-to-one-known-value check — the verifier has to look up whether the presented value was actually issued).storage: newNonceStorecontract +storage/memstorereference implementation, mirroringSessionStore/ReplayStore's issue-once/consume-once shape and contract-test pattern exactly.resource:Verifiergains full nonce-challenge behavior — genuinely optional (Dependencies.Noncesnil disables it entirely, zero behavior change from today, no visible-opt-out sentinel needed the wayRevocation's is, since RFC 9449 §8 makes this a resource-server MAY, not a security check this module treats as non-negotiable). A request whose proof carries no current, unconsumed nonce is rejected with a newErrorUseDPoPNoncecode and a freshly issued replacement (Error.Nonce()); a successfulVerifyproactively issues another one (AuthorizationContext.NextDPoPNonce) so steady-state traffic only pays the extra round trip once.cmd/conformance-as: new-dpop-nonce-challengeflag (off by default — the OIDF suite's own protected-resource caller isn't guaranteed to retry on the challenge) wires this end to end on the example/accounts//userinfoendpoints.Test plan
go build ./...,go vet ./...,go test ./...all cleanstorage.TestNonceStoreContract(+ memstore),internal/dpop.TestVerifyReturnsProofNonce,resource's nonce-challenge suite (missing/unknown/expired/valid nonce, single-use enforcement, proactive reissue, config validation)cmd/conformance-as.TestSmokeUserInfoWithDPoPNonceChallenge— full end-to-end integration test provingclient's existing nonce-retry logic (ResourceClient.Do) interoperates correctly with the new server-side challenge, entirely through the publicFetchUserInfoAPIconformance/scripts/run-all.sh) with the new flag left at its default (off): AS baseline (57 modules, 4465 conditions, 0 failures), AS message-signing (71 modules, 5975 conditions, 0 failures), RP baseline (22/22 PASSED), RP message-signing (28/28 PASSED) — no regressions