modelcontextprotocol / modelcontextprotocol/conformance

Authorization Server Auth: DPoP Token Binding (SEP-1932)

Open
#370 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
127
Forks
101
Avg merge
6d 1h
Merged PRs (30d)
7

Description

Overview

SEP-1932
adopts OAuth 2.0 Demonstrating Proof of Possession
(RFC 9449) as an optional MCP
authorization extension. For sender-constrained tokens to work, the OAuth
authorization server
must accept a DPoP proof at the token endpoint, bind the
issued access token to the client's public key, and advertise its DPoP support.

This issue covers authorization server conformance only. It validates that an
authorization server advertises dpop_signing_alg_values_supported, validates the
token-endpoint DPoP proof, issues a DPoP-bound token (token_type=DPoP with a
cnf.jkt confirmation), honours the optional nonce mechanism, and enforces
dpop_bound_access_tokens client registration.

Key properties of the authorization-server role:

  • The framework drives the authorization server as a DPoP client at the token
    endpoint, presenting valid and invalid proofs.
  • DPoP is adopted as defined in RFC 9449 — no MCP-specific extensions to the
    token-endpoint exchange.
  • This is distinct from client and resource-server conformance even though all
    three involve OAuth; the authorization server is tested in isolation.

Specification References

Scope

In scope — what the authorization server does:

  • Advertising dpop_signing_alg_values_supported (asymmetric algorithms only; no
    none) in its authorization server metadata.
  • Validating the DPoP proof presented on the token request (§4.3 as applicable to
    the token endpoint: typ, alg, signature, jwk without private key,
    htm=POST, htu=token endpoint, jti, iat window, nonce when required).
  • Issuing a DPoP-bound access token: token_type of DPoP and a cnf.jkt
    confirmation equal to the base64url JWK SHA-256 thumbprint of the proof key.
  • Token-endpoint nonce mechanism: 400 use_dpop_nonce + DPoP-Nonce, then
    accepting the retried request.
  • Honouring dpop_bound_access_tokens=true client registration by rejecting token
    requests that lack a DPoP proof. (Excluded — requires dynamic client registration, out of scope for these DPoP scenarios; see #396.)
  • Binding refresh tokens to the same key for public clients. (Deferred — not testable until the shared conformance test AS supports refresh tokens; tracked as a follow-up. See #396.)

Not in scope (covered elsewhere or by another role):

  • Resource-server proof validation and ath/cnf agreement at resource access —
    covered by the server conformance issue.
  • Client proof construction and nonce-retry behaviour — covered by the client
    conformance issue.

Changes Required

Conformance harness (framework acting as a DPoP client at the token endpoint)
  • The framework, acting as a DPoP client, performs a token request with a
    DPoP proof against the authorization server under test, then inspects the
    metadata, the token response (token_type), and the issued token's cnf.jkt.
  • Fixture generation for a valid proof keyed to a generated key pair, plus crafted
    invalid proofs (one per token-endpoint failure mode).
Helpers (helpers/)
  • DPoP proof builder with per-field overrides (reused from / shared with the
    server scenario helpers where practical).
  • JWK SHA-256 thumbprint helper to assert cnf.jkt equals the proof key
    thumbprint.
  • Metadata fetch/parse helper for dpop_signing_alg_values_supported.
Scenario (src/scenarios/authorization-server/)
  • A single scenario file implementing all checks below, registered in the
    authorization-server scenario list.
Acceptance test suite
  • Helper unit tests and scenario acceptance tests asserting each check passes for
    a conformant authorization server and fails for a deliberately non-conformant
    one.
Components that do not change
  • The resource-server (MCP server) path — the grant/token type does not change how
    a resource server validates an access token's audience.
  • OIDC discovery infrastructure beyond surfacing the new metadata field.

Checks to Cover

Positive
  • Metadata advertises dpop_signing_alg_values_supported as a JSON array of
    asymmetric JWS alg values; none is not present.
  • Token endpoint accepts a valid DPoP proof (typ=dpop+jwt, asymmetric alg,
    embedded public jwk, htm=POST, htu=token endpoint, jti, iat in
    window) and issues a token.
  • Issued token response sets token_type to DPoP.
  • Issued token carries a cnf.jkt equal to the base64url JWK SHA-256
    thumbprint of the proof's public key. (Verified for JWT access tokens; opaque/reference tokens are out of scope — cnf.jkt isn't observable without introspection; see #396.)
  • For a public client, an issued refresh token is bound to the same key. (Deferred to a follow-up. The shared test AS does not yet support refresh tokens; see #396.)
Negative (token-endpoint proof failures → 400 invalid_dpop_proof unless noted)
  • Proof signature does not verify against the embedded jwk.
  • typ is not dpop+jwt.
  • alg is none or a symmetric algorithm.
  • jwk contains a private key.
  • htm/htu do not match the token endpoint request.
  • iat is outside the acceptable window.
  • A required claim is missing (jti, htm, htu, iat).
Nonce behaviour (if the authorization server requires nonces)
  • Returns 400 use_dpop_nonce + DPoP-Nonce when a nonce is required and
    absent.
  • Accepts the retried token request carrying the matching nonce claim.
  • Rejects a proof whose nonce does not match a recently supplied value.
Client registration enforcement
  • With dpop_bound_access_tokens=true, the authorization server rejects a
    token request that does not include a DPoP proof. (Excluded — depends on dynamic client registration; see #396.)

Acceptance Criteria

  • A single scenario file in src/scenarios/authorization-server/ implements
    all checks above (one scenario, many checks).
  • Each check has both a passing case (conformant authorization server) and a
    deliberate failing case (crafted invalid proof / missing field) proven by the
    automated acceptance test suite — no check is vacuously passing.
  • Helper unit tests cover the proof builder, thumbprint, and metadata 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 authorization server implementation
    before the PR is submitted; SDK/AS baseline YAMLs updated where existing
    implementations do not yet support DPoP.

Out of Scope

  • Nonce cryptographic construction (e.g. AEAD-encrypted timestamps) — an
    implementation choice; only the observable use_dpop_nonce protocol is tested.
  • Full OAuth 2.1 / token-endpoint conformance unrelated to DPoP (PKCE, resource
    indicators, etc.) — covered by existing baseline authorization conformance.
  • Resource-server and client behaviour — covered by the separate server and
    client conformance issues.

Notes

Prepared with the help of Claude (Opus 4.8)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing helpers/ and the authorization-server scenarios under src/scenarios/authorization-server/, then inspect how scenarios are registered and run through the standard CLI. Implement the DPoP helper, thumbprint and metadata checks, proof failure cases, nonce behavior, and acceptance tests described here. Done means npm test covers passing and deliberately failing cases, with the scenario registered and documented implementations validated.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, authentication, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.