client/dpop: follow-ups from review (stacked on #394)#400
Draft
claude[bot] wants to merge 15 commits into
Draft
Conversation
commit: |
6ad0f76 to
9a1806c
Compare
…of a personal working branch
…e/iss, and surfaces the OAuth error body
… per RFC 3986/9449 §4.3
…ad of silently ignoring them
…reject unadvertised algs (RFC 9449 §5.1)
…(single-defect isolation)
…AS issuer key and the jkt bound at the token endpoint
…e iat-window to the spec's unconditional statement; align token-binding with RFC 9449 §6
…w) in htm/htu/iat rejection messages
…algs in the PRM (RFC 9728 §2)
…FC 9449 §11); negative variants opt in
…le HTTP, no SSE stream)
… servers MUST 401)
9a1806c to
724563b
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.
Requested by Paul Carleton · Slack thread
Follow-ups to #394 (now merged) from review feedback. Previously stacked on #401 (now merged), which registered the DPoP and WIF scenarios as the
io.modelcontextprotocol/auth/dpop/io.modelcontextprotocol/auth/wifextensions — this PR carries only the remaining review fixes and now targetsmaindirectly.Before / After
Before: the suite's spec link pointed at a personal working branch, the resource judge trusted whatever JWT the client presented (unverified
decodeJwt), a spurious trailing slash inhtupassed silently, the AS advertised ES256-only while enforcing ten algs, and failing implementers got rejection strings like "htu does not match the request URI" with no values to act on.After: the spec link is commit-pinned, the judge verifies the presented token against the test AS issuer key and the exact
cnf.jktbound at the token endpoint,htucomparison is strict RFC 3986/9449 §4.3 normalization, advertisement matches enforcement (with unadvertised algs rejected per RFC 9449 §5.1), the PRM advertisesdpop_bound_access_tokens_required/dpop_signing_alg_values_supported(RFC 9728 §2) so discovery-driven clients know DPoP is expected, and every htm/htu/iat rejection reports observed vs expected values (including the ±300s window).How (one commit per fix)
SpecReferences.DPOP_EXTENSIONandsep-1932.yaml'sspec_urlnow use a commit-pinned blob URL instead of thepieterkas-dpop-extensionbranch path, with a TODO to switch to the main-branch path once the doc merges.resourceparameter in both the authorization and token requests, validatesstate(andisswhen returned) on the callback, and surfaces the OAutherror/error_descriptionbody in token-request failures instead of bareHTTP 400.htunormalization — dropped the trailing-slash leniency in both validators; only RFC 3986 syntax-based normalization (case, default port, empty path) is applied. Tests updated: the trailing-slash case now pins rejection, with a genuine-normalization acceptance case added.dpopRequireNonce/dpopTokenRequestObson a DPoP-disabled test AS now throws instead of silently doing nothing.DPOP_ASYMMETRIC_ALGSconstant; the scenario's AS metadata advertises exactly what the validators enforce, and the token-endpoint validator rejects proofs signed with an alg outside the advertised list (RFC 9449 §5.1) with an asymmetric-only floor (§11.6).expectedSuccessSlugs, so a regression that breaks an unrelated check can't hide behind the subset assertion.cnf.jktagainst the jkt recorded at the token endpoint, closing the self-minted-token loophole.server-iat-windowrow is scoped to the spec's unconditional verbatim sentence (the stateless-server conditional moved to an excluded row);as-token-bindingreworded to RFC 9449 §6's actual requirement (cnf.jktfor JWT tokens, introspection permitted).htu/iatget their own messages distinct from mismatches.dpop_bound_access_tokens_required: trueanddpop_signing_alg_values_supported(RFC 9728 §2).generateDpopKeyPairdefaults toextractable: false(RFC 9449 §11 guidance); only theembedPrivateKeynegative variants opt in.proof.test.ts→dpopProof.test.ts; removed the pointer to nonexistent "project notes".Skipped
Testing
npm test— 37 files / 424 tests passnpm run check(tsgo + eslint + prettier) — cleanGenerated by Claude Code