Repository navigation
Authorization Server Auth: DPoP Token Binding (SEP-1932) #370
Description
Activity
- changed the title
[-]# Authorization Server Auth: DPoP Token Binding (SEP-1932)[/-][+]Authorization Server Auth: DPoP Token Binding (SEP-1932)[/+]on Jun 26, 2026 - added 16 commits that reference this issue
on Jul 2, 2026 - added a commit that references this issue
on Jul 10, 2026 Scope update - Public-client refresh-token binding (deferred from modelcontextprotocol/conformance#396)
The DPoP authorization-server PR (modelcontextprotocol/conformance#396) does not cover public-client refresh-token binding. The blocker is the shared conformance test AS (createAuthServer). It advertises refresh_token in grant_types_supported, but never issues a refresh_token and has no grant_type=refresh_token handler.
Without that, there's no way to build the conformant-vs-misbehaving pair needed to validate a refresh-binding check under the suite's "prove it passes and fails" rule, so the check can't be implemented and validated today.
Deferring this as a follow-up, to be added once the shared test AS gains refresh-token support. Recorded as an excluded: entry in sep-1932.yaml with this rationale. The in-scope items in this issue are annotated accordingly.
- added a commit that references this issue
on Sep 10, 2026 Scope update — dpop_bound_access_tokens=true enforcement (excluded from modelcontextprotocol/conformance#396)
The DPoP authorization-server PR (modelcontextprotocol/conformance#396) does not cover the client-registration enforcement check ("With dpop_bound_access_tokens=true, the authorization server rejects a token request that does not include a DPoP proof.")
This enforcement is a per-client registration policy (RFC 9449 Section 5.2). Exercising it requires registering a client with dpop_bound_access_tokens=true, which needs dynamic client registration. These DPoP AS scenarios use a pre-configured client and don't perform DCR (DCR is out of scope for them) so the check isn't exercisable here.
Intentionally excluded; recorded as an excluded: row in sep-1932.yaml.
Scope update — opaque (non-JWT) access tokens (out of scope for modelcontextprotocol/conformance#396)
The DPoP AS binding check verifies the issued token's cnf.jkt against the proof key, which is only observable when the access token is a JWT. If the AS issues an opaque/reference token, cnf.jkt can only be obtained via token introspection (RFC 9449 §6.2), which is optional. The scenario reports the binding check as SKIPPED for opaque tokens rather than mis-scoring it, consistent with the rest of the AS suite, which doesn't introspect or inspect token structure. Out of scope for this PR. To be revisited if the suite gains introspection support.
- added a commit that references this issue
on Oct 1, 2026
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 thetoken-endpoint DPoP proof, issues a DPoP-bound token (
token_type=DPoPwith acnf.jktconfirmation), honours the optional nonce mechanism, and enforcesdpop_bound_access_tokensclient registration.Key properties of the authorization-server role:
endpoint, presenting valid and invalid proofs.
token-endpoint exchange.
three involve OAuth; the authorization server is tested in isolation.
Specification References
cnf/jkt)dpop_signing_alg_values_supported, §5.2dpop_bound_access_tokensScope
In scope — what the authorization server does:
dpop_signing_alg_values_supported(asymmetric algorithms only; nonone) in its authorization server metadata.the token endpoint:
typ,alg, signature,jwkwithout private key,htm=POST,htu=token endpoint,jti,iatwindow,noncewhen required).token_typeofDPoPand acnf.jktconfirmation equal to the base64url JWK SHA-256 thumbprint of the proof key.
400 use_dpop_nonce+DPoP-Nonce, thenaccepting the retried request.
dpop_bound_access_tokens=trueclient registration by rejecting tokenrequests that lack a DPoP proof. (Excluded — requires dynamic client registration, out of scope for these DPoP scenarios; see Authorization Server: DPoP Support (SEP-1932) #396.)
Not in scope (covered elsewhere or by another role):
ath/cnfagreement at resource access —covered by the server conformance issue.
conformance issue.
Changes Required
Conformance harness (framework acting as a DPoP client at the token endpoint)
DPoP proof against the authorization server under test, then inspects the
metadata, the token response (
token_type), and the issued token'scnf.jkt.invalid proofs (one per token-endpoint failure mode).
Helpers (
helpers/)server scenario helpers where practical).
cnf.jktequals the proof keythumbprint.
dpop_signing_alg_values_supported.Scenario (
src/scenarios/authorization-server/)authorization-server scenario list.
Acceptance test suite
a conformant authorization server and fails for a deliberately non-conformant
one.
Components that do not change
a resource server validates an access token's audience.
Checks to Cover
Positive
dpop_signing_alg_values_supportedas a JSON array ofasymmetric JWS
algvalues;noneis not present.typ=dpop+jwt, asymmetricalg,embedded public
jwk,htm=POST,htu=token endpoint,jti,iatinwindow) and issues a token.
token_typetoDPoP.cnf.jktequal to the base64url JWK SHA-256thumbprint of the proof's public key. (Verified for JWT access tokens; opaque/reference tokens are out of scope —
cnf.jktisn't observable without introspection; see Authorization Server: DPoP Support (SEP-1932) #396.)Negative (token-endpoint proof failures →
400 invalid_dpop_proofunless noted)jwk.typis notdpop+jwt.algisnoneor a symmetric algorithm.jwkcontains a private key.htm/htudo not match the token endpoint request.iatis outside the acceptable window.jti,htm,htu,iat).Nonce behaviour (if the authorization server requires nonces)
400 use_dpop_nonce+DPoP-Noncewhen a nonce is required andabsent.
nonceclaim.noncedoes not match a recently supplied value.Client registration enforcement
dpop_bound_access_tokens=true, the authorization server rejects atoken request that does not include a DPoP proof. (Excluded — depends on dynamic client registration; see Authorization Server: DPoP Support (SEP-1932) #396.)
Acceptance Criteria
src/scenarios/authorization-server/implementsall checks above (one scenario, many checks).
deliberate failing case (crafted invalid proof / missing field) proven by the
automated acceptance test suite — no check is vacuously passing.
npm test.introduced.
before the PR is submitted; SDK/AS baseline YAMLs updated where existing
implementations do not yet support DPoP.
Out of Scope
implementation choice; only the observable
use_dpop_nonceprotocol is tested.indicators, etc.) — covered by existing baseline authorization conformance.
client conformance issues.
Notes
Prepared with the help of Claude (Opus 4.8)