Repository navigation
fix(server): check the DPoP nonce before consuming the request object - #825
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
osanderson
force-pushed
the
fix/dpop-nonce-before-request-object
branch
from
November 10, 2026 17:01
8b76871 to
445d2c6
Compare
PushAuthorizationRequest and BeginBackchannelAuthentication verified the request object (or signed authentication request), recording its jti as used, before verifying the request's DPoP proof and running the nonce check. With Dependencies.Nonces set, a first request without a nonce was refused with use_dpop_nonce after its request object had already been consumed, so the client's retry of the same request with a nonce-bearing proof (RFC 9449 §8 asks it to retry with the nonce, not to re-sign) was refused as a request object replay. Both endpoints now verify the optional DPoP proof, including its own jti replay record and the nonce, right after client authentication, in a shared verifyOptionalDPoPProof. The dpop_jkt binding steps stay where they were and take the verified proof's thumbprint. Error codes and messages are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Message Signing conformance-as instance ran with -dpop-nonce-challenge=false because the server consumed a pushed request object before its DPoP nonce challenge, so openid-client's nonce retry of the same signed request was refused as a replay. With the server now checking the nonce first, leg 3 runs with nonces on and the README's known-disagreement entry for it is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateAuthorizationParameters' cognitive complexity was just over the limit; the PKCE checks move into validatePKCEParameters unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateAuthorizationParameters was still just over the cognitive complexity limit; the response_type checks move into validateResponseType unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
osanderson
force-pushed
the
fix/dpop-nonce-before-request-object
branch
from
November 10, 2026 17:19
445d2c6 to
847009d
Compare
|
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
With
Dependencies.Noncesset,PushAuthorizationRequestandBeginBackchannelAuthenticationverified the request object (or signed authentication request), recording itsjtias used, before verifying the DPoP proof and running the nonce check. A first request without a nonce was refused withuse_dpop_nonceafter the request object had already been consumed, so the client's retry of the same request with a nonce-bearing proof was refused as a replay (invalid_request_objectat PAR,invalid_requestat CIBA). RFC 9449 §8 asks the client to retry with the nonce; nothing in RFC 9101 or FAPI 2.0 Message Signing asks it to re-sign the request object. Found by the openid-client interop suite (#818), whose client resends the same signed request object on its nonce retry.jtireplay record and the nonce) right after client authentication, before the request object, via a sharedverifyOptionalDPoPProof.reconcileParDPoPBinding/reconcileBackchannelDPoPBindingkeep thedpop_jktmatch / implicitdpop_jktlogic in the same place, taking the verified proof's thumbprint.srv-ciba-dpop-noncemutant moves to the shared helper inserver/par.goand now also runs the PAR nonce test.-dpop-nonce-challenge=falseworkaround and its README "Known disagreements" entry are removed).Not changed: the client assertion (
private_key_jwt) and the client attestation PoP are still verified and theirjtis recorded in client authentication, before any nonce check, at every endpoint. RFC 7523 §3 lets a server accept ajtionly once and clients sign a fresh assertion per request (openid-client does), so a client resending the same assertion on its nonce retry is the client's problem. The token endpoints (authorization code, refresh, CIBA token, client credentials) already check the DPoP nonce before redeeming any code, refresh token orauth_req_id.Tests
TestPushAuthorizationRequestNonceChallengeKeepsRequestObjectUsable: PAR with a request object and a nonce-less DPoP proof getsuse_dpop_nonce; the same request object with a fresh proof carrying the nonce succeeds; a second successful use is still refused asinvalid_request_object.TestBackchannelAuthenticationNonceChallengeKeepsRequestUsable: the same shape for a signed CIBA authentication request.interop/run-openid-client.shlocally: 6/6 legs pass with nonces on for Message Signing; leg 3 fails (invalid_request_object) against the previous server code.go test ./...,go vet ./...,golangci-lint run ./...(Go 1.26.10),sqlstoretests (SQLite), mutation check forsrv-ciba-dpop-nonce.🤖 Generated with Claude Code