Skip to content

Add a conceptual guide for production OAuth authorization - #1801

Open
anneheartrecord wants to merge 1 commit into
modelcontextprotocol:mainfrom
anneheartrecord:docs/production-oauth-guide
Open

Add a conceptual guide for production OAuth authorization#1801
anneheartrecord wants to merge 1 commit into
modelcontextprotocol:mainfrom
anneheartrecord:docs/production-oauth-guide

Conversation

@anneheartrecord

Copy link
Copy Markdown

Resolves #1052

PR Summary

Adds a conceptual guide covering how to do OAuth for a real deployment, so that the answer to "how should someone building a real world MCP server go about authentication?" isn't "read the test fixture."

The core confusion in #1052 is that TestOAuthServer looks like the thing you're supposed to build. It isn't — it's an authorization server, and an MCP server is a resource server. Once that split is clear, the production configuration is mostly AddJwtBearer pointed at an identity provider you already run, plus AddMcp() for the RFC 9728 metadata. The guide leads with that and then covers the parts that actually bite in production.

What's included

  1. New conceptual guide: docs/concepts/authorization/authorization.md
  2. Registered in conceptual navigation: docs/concepts/toc.yml (under Server features, next to HTTP Context)
  3. Added to the conceptual docs landing page: docs/concepts/index.md

Guide coverage

  1. Resource server vs. authorization server, and why TestOAuthServer is a fixture rather than a template
  2. What to require of an authorization server — audience/resource indicators, discovery metadata, PKCE, and dynamic client registration (the one most often overlooked, since MCP clients aren't yours to pre-register)
  3. A production AddJwtBearer + AddMcp configuration, with audience validation tied to the advertised resource
  4. Protected resource metadata: the default well-known endpoint, path scoping, when Resource must be set explicitly, and the reverse-proxy case — the resource identifier and the resource_metadata challenge URL are both derived from the request, so forwarded headers (including X-Forwarded-Prefix, and AllowedHosts to keep them un-spoofable) matter
  5. Per-request metadata via OnResourceMetadataRequest for multi-tenant servers
  6. Enforcement at both layers: RequireAuthorization() on the endpoint, and [Authorize] / [AllowAnonymous] per primitive via AddAuthorizationFilters() — including that listings are filtered, that custom list handlers are not, and that [AllowAnonymous] doesn't combine with endpoint-level gating
  7. Scope-based policies, and why RequireClaim("scope", ...) is the wrong tool for a space-delimited claim
  8. Claim freshness relative to sessions: [Authorize] checks re-read the principal per request, while ConfigureSessionOptions is once per session
  9. A table classifying the existing samples by whether they're production-shaped, and a closing checklist

Notes

  • Docs-only; no source or test changes.
  • Every API claim was checked against the source rather than inferred: McpAuthenticationHandler, McpAuthenticationOptions, ProtectedResourceMetadata, AuthorizationFilterSetup, HttpMcpServerBuilderExtensions, and AuthorizeAttributeTests.
  • I couldn't run make generate-docs locally (no .NET SDK on this machine), so the DocFX build is unverified. The three <xref:> API targets and the xref:identity / xref:stateless / xref:httpcontext links were checked by hand against the source and the sibling docs' uids, and the uid: authorization is unique. Worth a second look from CI.
  • Happy to split any of this out, drop sections, or turn it into a sample instead if that's the more useful shape.
  • [Documentation request] Authentication architecture suggestions #1494 asks for authentication architecture guidance and overlaps with this; it may be closeable by the same doc, or worth a follow-up section.

Documents how to protect an MCP server with OAuth 2.0 against an existing
identity provider, rather than standing up an authorization server the way
the TestOAuthServer fixture does.

Covers the resource server / authorization server split, what to require of
an authorization server, JwtBearer plus AddMcp configuration, protected
resource metadata behind a reverse proxy, per-request metadata for
multi-tenant servers, endpoint and per-primitive enforcement with
AddAuthorizationFilters, scope-based policies, and how claims are refreshed
relative to sessions.

@jeffhandley jeffhandley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First off, this is a really nice addition. The production framing is exactly what''s been missing. The resource-server versus authorization-server split, the "don''t copy the TestOAuthServer" warning, the proxy and forwarded-headers section, and the checklist at the end all read like they came from real deployment experience. Thank you for tackling this and putting together a comprehensive guide!

To help with my review I ran a couple of AI-based passes over the guide against the SDK source. I want to be upfront that I''m not fully confident in these findings. I haven''t stood up an end-to-end provider to confirm the runtime behavior, and some of this may be intentional simplification for a conceptual doc. I''m sharing them as things to sanity-check rather than firm change requests.

1. Inbound claim mapping in the samples. With ASP.NET Core JwtBearer, MapInboundClaims defaults to true, which I think renames roles to ClaimTypes.Role and scp to the long schema URI before the app sees them. If that''s right, the scope policy searching "scp" and possibly [Authorize(Roles = "Admin")] might not match as written. Would adding options.MapInboundClaims = false; to the AddJwtBearer block, or a note about it, make the samples more copy-paste safe? Genuinely unsure here. You may have tested this and found it works.

2. "Any spec-compliant OAuth 2.0 authorization server works." The JwtBearer config shown assumes JWT access tokens specifically, and Authority seems to drive OIDC discovery (/.well-known/openid-configuration) rather than the RFC 8414 oauth-authorization-server path the prose also mentions. Might be worth narrowing to "issues JWT access tokens" so nobody wires up an opaque-token provider and gets stuck. Low confidence, could be splitting hairs.

3. A few smaller things I wasn''t sure about:

  • The WWW-Authenticate-on-401 description reads a touch broad. From a quick look the resource_metadata header seems to be added only when the MCP scheme is the one challenging. The main sample sets that up correctly, so this may be a wording nit at most.
  • The session identity-pinning paragraph lists sub, NameIdentifier, and UPN. The code path I glanced at looked like it prefers NameIdentifier first and can end up with no id if none are present. Might be worth a caveat, or I may just be misreading it.

None of these are blockers from where I sit, and I could easily be wrong on any of them. Happy to be corrected, and thanks again for putting this together.

Note

This review was assisted by AI-based analysis of the guide against the SDK source. A human is reviewing the findings before acting on them.

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.

Practical OAuth implementation for production

2 participants