Skip to content

Server Auth: DPoP Proof Validation (SEP-1932) #369

Description

@PieterKas

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 iat window
    and standard claim validation; jti tracking and nonces are optional.

Specification References

Scope

In scope — what the MCP/resource server does:

  • Requiring the DPoP Authorization scheme and a DPoP proof header on
    protected requests.
  • Applying every RFC 9449 §4.3 proof-checking step.
  • Enforcing the ±5-minute iat acceptance window.
  • Confirming the access token is bound to the proof key (cnf/jkt agreement).
  • Returning correct 401 responses with a WWW-Authenticate: DPoP challenge
    (including the algs parameter and the appropriate error code).
  • 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 a DPoP proof to the server under
    test.
  • Fixture generation for: a valid key pair, a DPoP-bound access token whose
    cnf.jkt matches 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: wrong htu, wrong htm, stale/future iat,
    missing jti, typ != dpop+jwt, alg=none/symmetric, private key in jwk,
    bad signature, wrong/absent ath, duplicate DPoP header, mismatched
    cnf/jkt).
  • JWK SHA-256 thumbprint and ath helpers.

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 added DPoP proof 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 iat is 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 DPoP header field present.
  • DPoP header is not a single, well-formed JWT.
  • A required claim is missing (jti, htm, htu, iat).
  • typ JOSE header is not dpop+jwt.
  • alg is none or a symmetric algorithm (or otherwise unacceptable).
  • Signature does not verify against the embedded jwk.
  • jwk JOSE header contains a private key.
  • htm does not match the request method.
  • htu does not match the request target URI (ignoring query/fragment).
  • iat is outside the acceptable window (stale and far-future both rejected).
  • ath is 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 with error="invalid_token".
  • A DPoP-bound token presented with the Bearer scheme 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 a DPoP-Nonce header
    when it requires a nonce and none is present.
  • Server accepts the subsequent request carrying the matching nonce claim.
  • Server rejects a proof whose nonce does not match a recently supplied
    value.

Challenge format

  • 401 responses carry WWW-Authenticate: DPoP and SHOULD include the algs
    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/ath helpers.
  • 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 jti state-tracking / global replay store — optional per RFC 9449
    §11.1. A stateless server relying on the iat window 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions