Repository navigation
Server Auth: DPoP Proof Validation (SEP-1932) #369
Description
Overview
SEP-1932
adopts OAuth 2.0 Demonstrating Proof of Possession
(RFC 9449) as an optional MCP
authorization extension for sender-constrained access tokens. When a client
presents a DPoP-bound token, the MCP server (resource server) must verify a
DPoP proof on every request before granting access.
This issue covers MCP server conformance only. It validates that a server
acting as an OAuth 2.1 resource server correctly applies the RFC 9449 §4.3 proof
checking steps, enforces the ±5-minute iat acceptance window, returns correct
401 / WWW-Authenticate: DPoP challenges, and continues to enforce the baseline
token audience requirements.
Key properties of the server role:
- The framework drives the server as a DPoP client, presenting valid proofs
for positive checks and deliberately malformed proofs for negative checks. - The proposal adopts DPoP as defined in RFC 9449 — no MCP-specific extensions
to proof validation are expected. - A conformant stateless server provides replay protection via the
iatwindow
and standard claim validation;jtitracking and nonces are optional.
Specification References
- SEP-1932 PR: SEP-1932: DPoP Profile for MCP modelcontextprotocol#1932
- Detailed proposal: https://github.com/modelcontextprotocol/ext-auth/blob/pieterkas-dpop-extension/specification/draft/dpop-extension.mdx
- RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP): https://www.rfc-editor.org/rfc/rfc9449.html
- §4.2 DPoP Proof JWT Syntax, §4.3 Checking DPoP Proofs (the core checklist)
- §6 Public Key Confirmation (
cnf/jkt), §7 DPoP-bound access tokens - §7.1 The DPoP Authentication Scheme and
WWW-Authenticatechallenge - §9 Resource Server-Provided Nonce, §11.1 DPoP Proof Replay
- Baseline MCP Authorization specification (resource-server token validation and
audience binding): https://modelcontextprotocol.io/specification/draft/basic/authorization
Scope
In scope — what the MCP/resource server does:
- Requiring the
DPoPAuthorizationscheme and aDPoPproof header on
protected requests. - Applying every RFC 9449 §4.3 proof-checking step.
- Enforcing the ±5-minute
iatacceptance window. - Confirming the access token is bound to the proof key (
cnf/jktagreement). - Returning correct
401responses with aWWW-Authenticate: DPoPchallenge
(including thealgsparameter and the appropriateerrorcode). - Optional server-provided nonce issuance and enforcement.
- Continuing to enforce baseline token audience validation when DPoP is used.
Not in scope (covered elsewhere or by another role):
- Client proof construction and nonce-retry behaviour — covered by the client
conformance issue. - Authorization-server token binding, metadata, and token-endpoint nonce —
covered by the authorization-server conformance issue. - Cryptographic algorithm policy beyond rejecting
none/symmetric algorithms.
Changes Required
Conformance harness (framework acting as a DPoP client)
- The framework, acting as a DPoP client, presents a DPoP-bound access
token (Authorization: DPoP <token>) plus aDPoPproof to the server under
test. - Fixture generation for: a valid key pair, a DPoP-bound access token whose
cnf.jktmatches the proof key and whose audience matches the server under
test, and a matched valid proof. - A library of crafted invalid proofs, one per §4.3 failure mode, each
designed to trigger a specific rejection so the server's response is
predetermined.
Test authorization server (createAuthServer)
- Mint DPoP-bound access tokens (correct audience,
cnf.jkt) for the server under
test to validate. Only needed to the extent the server requires a trusted,
audience-correct token to reach the proof-validation path.
Helpers (helpers/)
- DPoP proof builder with per-field overrides (so each negative fixture can
perturb exactly one claim/header: wronghtu, wronghtm, stale/futureiat,
missingjti,typ != dpop+jwt,alg=none/symmetric, private key injwk,
bad signature, wrong/absentath, duplicateDPoPheader, mismatched
cnf/jkt). - JWK SHA-256 thumbprint and
athhelpers.
Scenario (src/scenarios/server/)
- A single scenario file implementing all checks below, registered in the server
scenario list.
Acceptance test suite
- Helper unit tests (proof builder field overrides, thumbprint,
ath), and
scenario acceptance tests asserting each check passes for a conformant server
and fails for a deliberately non-conformant one.
Components that do not change
- The MCP protocol/JSON-RPC interaction is unchanged; only the
Authorization
scheme and the addedDPoPproof verification differ.
Checks to Cover
Positive (valid proof accepted)
- A request with a valid DPoP-bound token and matching proof is accepted and
the MCP operation succeeds. - Server accepts a proof whose
iatis within the ±5-minute window.
Negative (RFC 9449 §4.3 — each rejected with 401 + WWW-Authenticate: DPoP error="invalid_dpop_proof" unless noted)
- More than one
DPoPheader field present. -
DPoPheader is not a single, well-formed JWT. - A required claim is missing (
jti,htm,htu,iat). -
typJOSE header is notdpop+jwt. -
algisnoneor a symmetric algorithm (or otherwise unacceptable). - Signature does not verify against the embedded
jwk. -
jwkJOSE header contains a private key. -
htmdoes not match the request method. -
htudoes not match the request target URI (ignoring query/fragment). -
iatis outside the acceptable window (stale and far-future both rejected). -
athis missing or does not equal the hash of the presented access token. - The access token's bound key (
cnf.jkt) does not match the proof's public
key → rejected witherror="invalid_token". - A DPoP-bound token presented with the
Bearerscheme is rejected. - Baseline: a token whose audience is not this server is rejected (
401),
even with an otherwise valid proof.
Optional nonce behaviour (only if the server advertises/requires nonces)
- Server issues
401 ... error="use_dpop_nonce"with aDPoP-Nonceheader
when it requires a nonce and none is present. - Server accepts the subsequent request carrying the matching
nonceclaim. - Server rejects a proof whose
noncedoes not match a recently supplied
value.
Challenge format
-
401responses carryWWW-Authenticate: DPoPand SHOULD include thealgs
parameter listing acceptable JWS algorithms.
Acceptance Criteria
- A single scenario file in
src/scenarios/server/implements all checks
above (one scenario, many checks). - Each §4.3 check has both a passing case (valid proof accepted) and a
deliberate failing case (crafted invalid proof rejected with the correct
status and error code) proven by the automated acceptance test suite. - Helper unit tests cover the proof builder's per-field overrides and the
thumbprint/athhelpers. - The acceptance test suite runs as part of
npm test. - Scenario runs through the standard CLI runner; no parallel entry point is
introduced. - Validated against at least one real SDK server implementation before the PR
is submitted; SDK baseline YAMLs updated where existing SDKs do not yet
support DPoP.
Out of Scope
- Required
jtistate-tracking / global replay store — optional per RFC 9449
§11.1. A stateless server relying on theiatwindow and claim validation is
conformant;jti-tracking is tested only as optional behaviour if advertised. - Server-side nonce cryptographic construction (e.g. AEAD-encrypted
timestamps) — an implementation choice; only the observable nonce challenge/
acceptance protocol is tested. - Client proof construction and authorization-server behaviour — covered by the
separate client and authorization-server conformance issues.
Notes
Prepared with the aid of Claude (Opus 4.8)
Activity
[-]# Server Auth: DPoP Proof Validation (SEP-1932)[/-][+]Server Auth: DPoP Proof Validation (SEP-1932)[/+]Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, m…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, m…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, m…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, m…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, m…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
Client Auth: DPoP Sender-Constrained Tokens (SEP-1932) (#394)
Add DPoP server proof-validation scenario (SEP-1932, modelcontextprot…
PieterKas commentedon Oct 1, 2026
Scope update — Bearer-downgrade rejection (#527; new requirement)
The Bearer-downgrade case in #395 (RejectsBearerScheme) accepted any non-2xx as a rejection, so a server that errored without authenticating was graded as rejecting. #527 tightens it: a rejection now requires HTTP 401 with a Bearer (RFC 6750 §3) or DPoP (RFC 9449 §7.1) challenge.
This behaviour had no normative basis — RFC 9449 §7.2 is descriptive only — so SEP-1932 is gaining an explicit sentence (MCP servers MUST NOT accept a DPoP-bound token presented under the Bearer scheme; rejection is 401 + Bearer/DPoP challenge). Recorded as sep-1932-server-reject-bearer-downgrade in sep-1932.yaml (now part of #395), reported untested until #527 re-homes the check onto that id.
PieterKas commentedon Oct 1, 2026
Scope update — WWW-Authenticate error code (moved to #528 )
This issue's scope includes returning "the appropriate error code" on the WWW-Authenticate challenge. #395 does not inspect error codes; that item is implemented by #528 (stacked on #527), which adds a SHOULD-graded sep-1932-server-error-code check per RFC 9449 §7.1 — invalid_dpop_proof for proof-validation failures, invalid_token for token failures (e.g. wrong audience), with mismatches reported as WARNING.
SEP-1932 is gaining explicit error-code guidance to back the check's expectations (modelcontextprotocol/ext-auth#33), since RFC 9449 §7.1 alone leaves room for invalid_request on request-shape defects.
Tracked in #528, not here.
PieterKas commentedon Oct 2, 2026
Scope update —
algsadvertisement and DPoP discovery (moved to #529)This issue's scope includes the
algsparameter on theWWW-Authenticatechallenge. #395 does not cover it; #529 adds discovery checks (challenge advertisement, Protected Resource Metadata DPoP fields, and an advertisement/enforcement consistency probe). Review feedback on #529 is in: its grading is being aligned with the advertisement section proposed for SEP-1932 in modelcontextprotocol/ext-auth#33.Since #395 auto-closes this issue on merge: the remaining scope items above are tracked by the open stacked PRs (#527 → #528 → #529), so closure here does not mean the full written scope is covered.