Skip to content

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

Description

@PieterKas

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.
  • Binding refresh tokens to the same key for public clients.

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.
  • For a public client, an issued refresh token is bound to the same key.

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.

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)

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