Skip to content

[release/1.x] Require OAuth PKCE metadata by default - #1949

Open
halter73 wants to merge 2 commits into
release/1.xfrom
halter73-backport-pkce-metadata-enforcement
Open

halter73 wants to merge 2 commits into
release/1.xfrom
halter73-backport-pkce-metadata-enforcement

Conversation

@halter73

Copy link
Copy Markdown
Contributor

Summary

Backport #1700 (7369a26) to release/1.x, with an explicitly approved 1.x security-default adaptation for metadata-free legacy servers.

The first commit preserves #1700's production change and four regression tests, with cherry-pick attribution. Two test-file conflicts were resolved without importing unrelated scope-selection/offline-access changes. The second commit adds the opt-in compatibility switch described below.

  • Require discovered authorization server metadata to advertise S256 in code_challenge_methods_supported; no longer synthesize that field for discovered documents.
  • Continue trying alternative well-known endpoints when a document fails PKCE validation, logging the specific failure. A later eligible document can still succeed.
  • Never replace a discovered document that fails PKCE validation with synthetic legacy defaults, regardless of the switch.
  • Add no response-aware callback APIs, state-validation backport, or RFC 9207 issuer-validation behavior. No main-branch change is included.

This supersedes the proposed state-validation backport #1948 with a focused 1.x approach, rather than importing the callback architecture from #1605/#1726.

Deliberate 1.x security-default adaptation

Original #1700 retains the MCP 2025-03-26 default-endpoint fallback when protected resource metadata is unavailable and authorization server metadata discovery finds no usable document. That fallback synthesizes S256, which does not establish PKCE support. Because the existing 1.x callback API returns only a code and cannot provide response state for SDK validation, this backport makes that fallback disabled by default.

ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback is a default-off, process-wide AppContext switch. When absent or false, the client rejects that metadata-free path before requesting an authorization code or exchanging tokens. A host can explicitly restore only the original eligible fallback at startup:

AppContext.SetSwitch(
    "ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback",
    true);

The switch retains the original resourceUri is null guard, /authorize, /token, and /register defaults, and unchanged S256 challenge/verifier behavior. It cannot bypass discovered metadata that omits S256, and the client logs a warning whenever it actually uses this unverified fallback.

Enabling the switch is not verification of PKCE support and does not add state validation. Hosts must independently establish that a trusted deployment correctly enforces PKCE, including downgrade defenses. This is not a per-server allowlist: it affects all OAuth clients in the process. Do not enable it in a process that also connects to untrusted authorization servers or toggle it around individual requests.

The API remarks, transport guide, and protected-client sample documentation explain configuration and security requirements. The conformance client opts in only for its trusted auth/2025-03-26-oauth-endpoint-fallback fixture; other scenarios retain the secure default.

Security rationale

RFC 9700 §2.1 and §4.7.1 permit relying on PKCE for CSRF protection when authorization-server support has been ensured. #1700 strengthens that prerequisite for existing code-only callbacks without importing a new callback API.

This verifies advertised support, not the server's actual verifier or downgrade implementation. Authorization servers must correctly enforce PKCE, including the §4.8.2 downgrade defenses. This is not a claim that state validation is universally unnecessary or that this approach is security-equivalent to every state-validation design.

Release note

Compatibility tightening: OAuth authentication now requires discovered authorization server metadata to advertise S256 PKCE support, even if the server implements PKCE without advertising it. Metadata-free legacy authentication is also rejected by default. Trusted 2025-03-26 deployments with independently established PKCE support can explicitly restore the historical no-metadata fallback using the process-wide ModelContextProtocol.Authentication.AllowLegacyOAuthMetadataFallback AppContext switch; enabling it emits a warning and never bypasses PKCE validation of discovered metadata.

Validation

  • dotnet build --no-restore --nologo --verbosity quiet: zero warnings/errors.
  • OAuth and client conformance tests: 68 passed on each of .NET 8, 9, and 10.
  • Complete non-manual solution suite: 8,567 passed, 262 skipped, zero failures across .NET 8/9/10 and .NET Framework 4.7.2.
  • Coverage includes absent/false/true switch states, eligible legacy fallback only when enabled, rejection of discovered missing/plain-only PKCE support even when enabled, alternative discovery success, preservation of the protected-resource-metadata guard, warning emission only when fallback is used, and no authorization/token requests on rejected paths. Global AppContext data is isolated in a nonparallel collection and restored, including the absent state; switch values are not cached.

Local process tests used a test-process-only current-directory PATH entry for Windows executable lookup. Pinned npm dependencies were installed by the existing test harness. No machine-wide settings or repository test behavior were weakened.

PranavSenthilnathan and others added 2 commits October 21, 2026 18:48
(cherry picked from commit 7369a26)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the release/1.x OAuth client fail-closed when PKCE support cannot be verified from authorization server metadata. Retain the historical no-metadata fallback only behind an explicit process-wide compatibility switch, with a warning and deployment requirements.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants