* Add DPoP client conformance scenario + shared DPoP helpers (SEP-1932, #368) Client-anchored packaging: carries the shared DPoP foundation (dpopProof / dpopToken helpers, the createAuthServer DPoP core, and sep-1932.yaml) together with the client scenario, so it can merge first; the server (#369) and authorization-server (#370) scenarios are follow-ups that depend on it. DPoP is treated as a draft/not-yet-official feature (mirroring the WIF SEP-1933 scenario): the scenario's source is `introducedIn: DRAFT_PROTOCOL_VERSION` and it is registered in draftScenariosList (informational, not scored). It is NOT registered in EXTENSION_IDS, since the ext-auth DPoP extension is still on a branch rather than merged. Client checks (the two extension MUSTs, RFC 9449 §7.1 / §4.2-4.3): - sep-1932-client-dpop-auth-scheme — token presented with the DPoP scheme. - sep-1932-client-fresh-proof — a fresh, well-formed DPoP proof per request. The test MCP server judges the client via an opt-in DPoP middleware (dpopResourceAuth) passed through createServer's authMiddleware hook; a compliant example client (covered by the Client Draft Scenarios loop) plus deliberately- broken bearer/replay variants drive the pass/fail acceptance tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * client/dpop: split into nonce-less baseline + nonce-required scenario Server-provided nonces are optional in RFC 9449 (AS §8 "MAY", RS §9 "can also choose"), and the nonce-less flow is the common case. The single auth/dpop scenario hard-wired nonce enforcement on both the test AS and MCP server, so a plain nonce-less proof was never accepted end-to-end and a client that only does plain proofs was never exercised on its happy path. Parameterize DPoPClientScenario with `requireNonce` and register it twice: - auth/dpop (requireNonce=false) — nonce-less baseline; emits the three baseline checks (token-request-proof, dpop-auth-scheme, fresh-proof). - auth/dpop-nonce (requireNonce=true) — prior behavior; adds as-nonce and rs-nonce (five checks). Check IDs are reused (no new IDs, no sep-1932.yaml change); the two nonce checks are emitted only by auth/dpop-nonce, so traceability stays intact. Add auth-test-dpop-no-nonce.ts (nonce-incapable but otherwise compliant) asserting SUCCESS against auth/dpop — proof that a client with no nonce handling still completes DPoP when the server does not require a nonce. Baseline negatives stay on auth/dpop (nonce-independent); the two nonce negatives are re-pointed to auth/dpop-nonce. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * client/dpop: round-4 nonce-surface correctness fixes Address review findings on the auth/dpop-nonce posture: - Record the token-request proof observation BEFORE the §8 nonce challenge, so a client that is challenged but does not retry is no longer mis-reported by token-request-proof as "never completed a token request" (its proof was just validated). (#2) - Record the resource-request jti/replay on ANY valid proof (before the §9 nonce gate), so fresh-proof is never asserted on zero evidence and a client that never honours the RS nonce fails ONLY rs-nonce. (#5) - Require challengeIssued && honored for as-nonce/rs-nonce SUCCESS, closing a vacuous-pass path (a client that pre-sends the nonce is never challenged). (#3a) - Gate the AS nonce observation on the authorization_code exchange, matching recordTokenRequestProof (a refresh exchange must not satisfy §8). (#3c) - Collapse duplicate shared token-flow check IDs (token-request, pkce-*) that the §8 round-trip re-POST would otherwise emit twice. (#4) - Example client: retry the token request only on a use_dpop_nonce error code, not any 400 carrying a DPoP-Nonce header (consistent with the RS path). (#6) - Fix the DpopTokenRequestObservation docstring ("last write wins" → sticky-failure). (#7) Adds an expectedSuccessSlugs option to the client test helper and tightens the two nonce negative tests to pin the now-clean behavior (token-request-proof SUCCESS for no-as-nonce; only rs-nonce fails for no-rs-nonce). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * client/dpop: round-5 fixes — dedupe masking, wording, replay decision - dedupeSharedChecks was masking failures: it kept the last occurrence of a duplicate check ID regardless of status and ran in BOTH postures, so a FAILURE on a challenged POST (or an earlier attempt in a restarted flow) was silently dropped. Replace with an exported, unit-tested collapseDuplicateChecks that prefers the MOST-SEVERE occurrence, and only apply it in the nonce posture (the baseline keeps genuinely distinct repeated attempts). (R5 #1) - Reword the token-request-proof description: it can be SUCCESS for a client that presented a valid proof but never obtained a token (challenged, no retry), so it no longer claims "obtaining a DPoP-bound access token". (R5 #2) - Reword the as-nonce/rs-nonce failure messages to cover a client that DID retry but whose retry proof was rejected (not only "did not retry"). (R5 #3) - Document the deliberate replay decision: recording jti on challenged requests means re-sending the identical proof across the challenge/retry boundary trips replay detection — a genuine RFC 9449 §4.2 jti-uniqueness violation. (R5 #5) Adds src/scenarios/client/auth/dpop.test.ts unit-testing collapseDuplicateChecks (prefers failures, keeps INFO, preserves order) — the dedupe fix is now pinned. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * client/dpop: round-6 polish — pin collapse wiring; docstring - Add an integration assertion that the §8/§9 double-POST is collapsed to one token-request/pkce-* entry each in auth/dpop-nonce, so the requireNonce gating of collapseDuplicateChecks can't be silently deleted or inverted (previously mutation-survivable). runClientAgainstScenario now returns the emitted checks so callers can make occurrence-level assertions (backward-compatible). - Docstring: note the severity ladder ranks SKIPPED below SUCCESS, and that equal-severity ties keep the last occurrence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
150 lines
6.1 KiB
TypeScript
150 lines
6.1 KiB
TypeScript
import { SpecReference } from '../../../types';
|
|
|
|
export const SpecReferences: { [key: string]: SpecReference } = {
|
|
RFC_PRM_DISCOVERY: {
|
|
id: 'RFC-9728',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9728.html#section-3.1'
|
|
},
|
|
RFC_AUTH_SERVER_METADATA_REQUEST: {
|
|
id: 'RFC-8414-metadata-request',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc8414.html#section-3.1'
|
|
},
|
|
LEGACY_2025_03_26_AUTH_DISCOVERY: {
|
|
id: 'MCP-2025-03-26-Authorization-metadata-discovery',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#server-metadata-discovery'
|
|
},
|
|
LEGACY_2025_03_26_AUTH_URL_FALLBACK: {
|
|
id: 'MCP-2025-03-26-Authorization-metadata-url-fallback',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization#fallbacks-for-servers-without-metadata-discovery'
|
|
},
|
|
MCP_PRM_DISCOVERY: {
|
|
id: 'MCP-2025-06-18-PRM-discovery',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#protected-resource-metadata-discovery-requirements'
|
|
},
|
|
MCP_AUTH_DISCOVERY: {
|
|
id: 'MCP-Authorization-metadata-discovery',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-server-metadata-discovery'
|
|
},
|
|
MCP_DCR: {
|
|
id: 'MCP-Dynamic-client-registration',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/client#dynamic-client-registration'
|
|
},
|
|
OAUTH_2_1_AUTHORIZATION_ENDPOINT: {
|
|
id: 'OAUTH-2.1-authorization-endpoint',
|
|
url: 'https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-authorization-endpoint'
|
|
},
|
|
OAUTH_2_1_TOKEN: {
|
|
id: 'OAUTH-2.1-token-request',
|
|
url: 'https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-token-request'
|
|
},
|
|
MCP_ACCESS_TOKEN_USAGE: {
|
|
id: 'MCP-Access-token-usage',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#access-token-usage'
|
|
},
|
|
MCP_SCOPE_SELECTION_STRATEGY: {
|
|
id: 'MCP-Scope-selection-strategy',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#scope-selection-strategy'
|
|
},
|
|
MCP_SCOPE_CHALLENGE_HANDLING: {
|
|
id: 'MCP-Scope-challenge-handling',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#scope-challenge-handling'
|
|
},
|
|
MCP_AUTH_ERROR_HANDLING: {
|
|
id: 'MCP-Auth-error-handling',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#error-handling'
|
|
},
|
|
MCP_CLIENT_ID_METADATA_DOCUMENTS: {
|
|
id: 'MCP-Client-ID-Metadata-Documents',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#client-id-metadata-documents'
|
|
},
|
|
IETF_CIMD: {
|
|
id: 'IETF-OAuth-Client-ID-Metadata-Document',
|
|
url: 'https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00'
|
|
},
|
|
RFC_JWT_CLIENT_AUTH: {
|
|
id: 'RFC-7523-JWT-Client-Auth',
|
|
url: 'https://datatracker.ietf.org/doc/html/rfc7523#section-2.2'
|
|
},
|
|
OAUTH_2_1_CLIENT_CREDENTIALS: {
|
|
id: 'OAUTH-2.1-client-credentials-grant',
|
|
url: 'https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-4.2'
|
|
},
|
|
SEP_1046_CLIENT_CREDENTIALS: {
|
|
id: 'SEP-1046-Client-Credentials',
|
|
url: 'https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx'
|
|
},
|
|
RFC_8707_RESOURCE_INDICATORS: {
|
|
id: 'RFC-8707-Resource-Indicators',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc8707.html'
|
|
},
|
|
MCP_RESOURCE_PARAMETER: {
|
|
id: 'MCP-Resource-Parameter-Implementation',
|
|
url: 'https://modelcontextprotocol.io/specification/draft/basic/authorization#resource-parameter-implementation'
|
|
},
|
|
MCP_PREREGISTRATION: {
|
|
id: 'MCP-Preregistration',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#preregistration'
|
|
},
|
|
MCP_PKCE: {
|
|
id: 'MCP-PKCE-requirement',
|
|
url: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#authorization-code-protection'
|
|
},
|
|
// TODO: Update to MCP spec URL once SEP-2468 (modelcontextprotocol/modelcontextprotocol#2468) is merged
|
|
RFC_9207_ISS_PARAMETER: {
|
|
id: 'RFC-9207-iss-parameter',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9207.html#section-2'
|
|
},
|
|
RFC_8693_TOKEN_EXCHANGE: {
|
|
id: 'RFC-8693-Token-Exchange',
|
|
url: 'https://datatracker.ietf.org/doc/html/rfc8693'
|
|
},
|
|
RFC_7523_JWT_BEARER: {
|
|
id: 'RFC-7523-JWT-Bearer-Grant',
|
|
url: 'https://datatracker.ietf.org/doc/html/rfc7523'
|
|
},
|
|
SEP_990_ENTERPRISE_OAUTH: {
|
|
id: 'SEP-990-Enterprise-Managed-OAuth',
|
|
url: 'https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/enterprise-managed-authorization.mdx'
|
|
},
|
|
SEP_2207_REFRESH_TOKEN_GUIDANCE: {
|
|
id: 'SEP-2207-Refresh-Token-Guidance',
|
|
url: 'https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2207'
|
|
},
|
|
SEP_1933_WIF: {
|
|
id: 'SEP-1933-Workload-Identity-Federation',
|
|
url: 'https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1933'
|
|
},
|
|
// DPoP (SEP-1932 / RFC 9449) — client concerns.
|
|
SEP_1932_DPOP: {
|
|
id: 'SEP-1932-DPoP',
|
|
url: 'https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1932'
|
|
},
|
|
DPOP_EXTENSION: {
|
|
id: 'MCP-DPoP-Extension',
|
|
url: 'https://github.com/modelcontextprotocol/ext-auth/blob/pieterkas-dpop-extension/specification/draft/dpop-extension.mdx'
|
|
},
|
|
RFC_9449_PROOF_SYNTAX: {
|
|
id: 'RFC-9449-dpop-proof-jwt-syntax',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-4.2'
|
|
},
|
|
RFC_9449_CHECKING_PROOFS: {
|
|
id: 'RFC-9449-checking-dpop-proofs',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-4.3'
|
|
},
|
|
RFC_9449_AUTH_SCHEME: {
|
|
id: 'RFC-9449-dpop-authentication-scheme',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-7.1'
|
|
},
|
|
RFC_9449_TOKEN_REQUEST: {
|
|
id: 'RFC-9449-dpop-access-token-request',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-5'
|
|
},
|
|
RFC_9449_AS_NONCE: {
|
|
id: 'RFC-9449-authorization-server-provided-nonce',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-8'
|
|
},
|
|
RFC_9449_RS_NONCE: {
|
|
id: 'RFC-9449-resource-server-provided-nonce',
|
|
url: 'https://www.rfc-editor.org/rfc/rfc9449.html#section-9'
|
|
}
|
|
};
|