Back
Kevin Riedl

13 min read · 23 Jul 2026

Next

Enterprise MCP Authorization Architecture: A Multi-Tenant Reference Design

The Model Context Protocol spec finally says something concrete about authorization: use OAuth 2.1, bind every token to a specific audience with resource indicators, and never pass a client token through to a downstream API. The requirements are real. The problem is that they are scattered across the core spec, a security best-practices document, and half a dozen IETF RFCs, and almost every page that ranks for these terms is a security-vendor checklist that stops at the single-server case.

This is the reference design we use when a client needs an MCP server that serves more than one tenant, plugs into an enterprise identity provider, and survives a procurement security review. It is vendor-neutral, it is anchored to the 2025-11-25 spec revision, and it draws the one diagram nobody else draws: the full trust boundary from identity provider to per-tenant data plane. Written from Wavect's AI product and security-hardening work.

Designing an enterprise MCP server?

 Talk to Wavect

Who owns what: the MCP server is a resource server, not an authorization server

The single most common mistake is building the MCP server as its own login system. It is not. Under the current spec the MCP server is an OAuth 2.1 resource server. Its job is to validate tokens, not to issue them. A separate authorization server, which in an enterprise is your identity provider (Entra ID, Okta, Auth0, Keycloak, Ping), interacts with the user and issues access tokens.

This split is recent. The first authorization spec (2025-03-26) expected the MCP server to act as both authorization server and resource server. The 2025-06-18 revision separated the roles through pull request 284, and that separation is what makes enterprise single sign-on possible at all. The authorization server may still be co-located with the resource server, but the mandated responsibility of the MCP server is token validation. Get this role model right and everything downstream falls into place. Get it wrong and you rebuild identity badly.

What does the current spec actually require?

Two caveats before the table. First, authorization in MCP is optional and is defined only for HTTP transports. A server behind a STDIO transport should not follow this spec at all; it pulls credentials from the environment. Second, OAuth 2.1 is still an IETF draft, not a published RFC, so do not cite an RFC number for it. Here is the standards surface for the 2025-11-25 revision.

StandardReferenceRole in MCPLevel
OAuth 2.1draft-ietf-oauth-v2-1Core authorization frameworkAuth server MUST
Protected Resource MetadataRFC 9728Server points client to its auth serverResource server MUST
Resource IndicatorsRFC 8707Binds the token to one audienceClient MUST
Authorization Server MetadataRFC 8414Auth server discoveryMUST (one of this or OIDC)
OpenID Connect DiscoveryOIDC Core 1.0Alternative auth server discoveryMUST (one of this or 8414)
PKCEOAuth 2.1 Sec 7.5.2Protects the authorization codeClient MUST, S256
Client ID Metadata Documentsdraft-ietf-oauth-client-id-metadata-documentURL as client_idSHOULD (new in 2025-11-25)
Dynamic Client RegistrationRFC 7591Automated client registrationMAY (was SHOULD)
Bearer token usageRFC 6750Challenge and insufficient_scopeReferenced

Note the two version deltas that trip people up. The 2025-11-25 revision downgraded Dynamic Client Registration from SHOULD to MAY, and it added a mandatory PKCE S256 method plus a requirement that the client verify PKCE support before proceeding. If the auth server metadata does not advertise code_challenge_methods_supported, a compliant client must refuse to start the flow.

How does the authorization flow work end to end?

The whole handshake is a discovery step followed by a standard authorization code flow. The client hits the server without a token, gets told where to authenticate, authenticates, and comes back with a token minted for this specific server.

MCP OAuth 2.1 authorization sequence: 401 challenge, protected-resource metadata discovery, authorization code flow with PKCE and a resource indicator, then an audience-bound bearer token validated by the resource server

Two mandatory details live inside that diagram. On the challenge, the server must return 401 Unauthorized with a WWW-Authenticate header carrying the resource_metadata URL, per RFC 9728. The 2025-11-25 revision also lets the client fall back to probing the well-known URI directly. On the authorization and token requests, the client must send a resource parameter set to the canonical URI of the MCP server, and it must send it whether or not the auth server claims to support it.

Why is token passthrough prohibited, and how do you validate audience?

This is the security heart of the spec, and it is where most home-grown servers fail. The rule is blunt: the MCP server must validate that each access token was issued for it as the intended audience, and it must reject anything else. It must not accept a token minted for another service and it must not forward the token it received to a downstream API. That last anti-pattern is token passthrough, and the spec bans it outright.

The reason is the confused deputy problem. If your server accepts and forwards tokens it did not verify, a token leaked or issued for one service becomes a skeleton key for another, rate limits keyed on audience are bypassed, and the downstream audit trail shows the wrong identity. Audience validation is one claim check, and it is the line between a resource server and a liability.

on every tool call:
  token = bearer_from_authorization_header()   # never from a session
  claims = verify_signature(token, jwks_of(trusted_issuer))
  assert claims.iss == expected_issuer          # pinned per tenant
  assert this_server_uri in claims.aud          # RFC 8707 audience
  assert not expired(claims) and not before(claims)
  assert required_scope_for(tool) in claims.scope
  tenant = claims["tenant"] or claims["org_id"]
  enforce_tenant(tenant)                         # every query, every secret

Notice what is not there: no session lookup for authentication. The spec is explicit that servers must verify every inbound request and must not use sessions for authentication. Session identifiers, if you use them at all, must be non-deterministic and bound to user identity so a guessed identifier cannot impersonate another tenant.

How does the server reach downstream APIs without leaking the token?

If the MCP server needs to call a downstream API, it acts as an OAuth client to that API and uses a separate token minted by the upstream authorization server. The spec tells you what not to do (do not pass through the inbound token) but leaves the how to the ecosystem. The dominant pattern is token exchange, defined in RFC 8693: the server swaps the inbound token, whose audience is the MCP server, for a new token whose audience is the downstream API.

The server plays a dual role here: resource server to the client, OAuth client to the API. Prefer delegation over impersonation so the original user stays in the sub claim and the acting chain is recorded in the act claim, which is what keeps your audit trail honest across the hop. Token exchange and on-behalf-of flows sit outside the core MCP spec, so treat them as an identity-provider or gateway responsibility, not an MCP requirement.

What does the multi-tenant reference architecture look like?

Here is the topology. One identity provider issues audience-bound tokens with a realm per tenant. Clients present those tokens to a shared resource server or gateway inside the enterprise trust boundary. The server validates audience, enforces per-tool scope, pins the tenant realm, and only then reaches tools, data, and downstream APIs, each isolated per tenant.

Multi-tenant MCP authorization architecture: per-tenant clients get audience-bound tokens from a shared identity provider, present them to an MCP gateway that validates audience, enforces per-tool scope and pins the tenant realm, then reaches downstream APIs via RFC 8693 token exchange while data plane, credential vault and audit log stay per-tenant

The mistake in most multi-tenant write-ups is treating isolation as a database concern. It is not. Tenancy has to be enforced at every layer, and the tenant identity comes from a verified token claim, never from a tool input the model can influence. The table below is the checklist we walk through per layer.

LayerIsolation controlFailure if skipped
IdentityPin issuer and realm per tenant; reject tokens from other realmsTenant A logs in via Tenant B's realm
TokenValidate audience (RFC 8707) and expiry on every requestToken minted elsewhere is accepted
ScopeLeast-privilege scopes mapped from IdP groups and rolesRead role performs a write
ToolFilter visible tools by tenant tier and scopeAgent discovers a tool it cannot use safely
CredentialPer-tenant secrets in a vault, opaque to model and clientOne tenant's API key serves another
DataRow-level security keyed on the tenant claimCross-tenant row read
AuditPer-tenant, immutable logs with correlation IDsNo attribution during an incident
Kevin Riedl

"The tenant identity is a verified token claim. The moment it comes from a tool argument the model can write, your isolation is theatre."

How do per-tool scopes and step-up authorization work?

The core protocol handles scopes at the OAuth layer, not per individual tool, so true per-tool authorization is something you build on top. The spec gives you the primitives. Start minimal and avoid omnibus scopes like all or full-access. When a tool needs more than the current token grants, the 2025-11-25 revision defines a clean step-up: the server returns 403 with WWW-Authenticate: Bearer error="insufficient_scope" and the required scope, the client runs an incremental authorization for just that scope, and retries.

In practice we map identity-provider groups and SCIM roles to scopes, then gate each tool on a required scope. An analyst role carries invoices:read and sees the read tools; it never carries ledger:write. This is the same discipline we apply in an authorization review, and it is what a procurement team actually tests.

How do you add enterprise SSO to an MCP server?

There are two shapes, and the choice drives most of your operational cost.

The first is OAuth per server: each MCP server points its authorization_servers metadata at the workforce identity provider. Clean for one or two servers, repetitive at fleet scale. The second is an MCP gateway: a single policy-enforced ingress in front of every server that centralises discovery, token validation, scope mapping, token exchange, and audit. For an enterprise running many servers across many tenants, the gateway is usually the right answer because it gives you one place to enforce isolation and one place to audit.

On protocols: OIDC is first-class in the 2025-11-25 revision, which requires the auth server to support RFC 8414 metadata or OpenID Connect Discovery, and requires clients to try both. There is no native SAML in MCP. A SAML-only enterprise bridges through its identity provider or a broker that exposes an OAuth or OIDC front for the MCP server.

How do you govern client registration and credential lifetimes?

Dynamic Client Registration was the soft spot in early deployments because it let arbitrary clients enrol. The 2025-11-25 revision demotes it to MAY and prefers a three-tier model: pre-registered credentials for known clients, then Client ID Metadata Documents where the client uses an HTTPS URL as its client_id, then DCR as a fallback. If you allow either of the latter two, allowlist issuers and watch for the SSRF and localhost-redirect risks the spec calls out. Enterprises should tie registration to a tenant and put unknown clients through an admin-review queue.

Credential lifetimes follow OAuth 2.1: issue short-lived access tokens, rotate refresh tokens for public clients, store tokens securely, keep tokens out of URL query strings, and send the bearer token on every request. Concrete defaults we use as a starting point:

CredentialTypical lifetimeRotation
Access token (client to server)5 to 15 minutesRe-mint from refresh token
Refresh token (public client)Hours to days, slidingRotate on every use
Downstream token (RFC 8693)Minutes, per call or per sessionRe-exchange, never cache broadly
Per-tenant secret in vaultLong, but revocableScheduled plus on suspected leak

What audit events must you capture?

The core spec has no dedicated audit-logging requirement, but the token-passthrough analysis makes the case for you: passthrough breaks the audit trail because downstream logs show the wrong identity. In a multi-tenant system, audit is how you prove isolation held. Log these, per tenant, with a correlation ID that survives the downstream hop.

  • Token validation outcomes: accepted, rejected on audience, rejected on issuer, expired.
  • Authorization decisions: tool invoked, scope required, scope granted, allow or deny.
  • Step-up events: scope requested and the subset granted.
  • Token exchange: which downstream audience was minted, for which subject and actor.
  • Tenant context: the verified tenant claim on every entry, so a query is attributable to a tenant, a user, and a tool.

Per server, gateway, or managed identity: which do you pick?

A quick decision matrix for the three viable topologies.

ApproachBest whenCost
OAuth per serverOne or two servers, one tenantLow setup, poor at scale
MCP gatewayMany servers or many tenantsHigher setup, one enforcement point
Managed IdP integrationYou already live in Entra or OktaVendor lock, fastest compliance

Going from a single-tenant server to a multi-tenant one is not a rewrite if you sequence it: introduce the tenant claim and pin the issuer first, move secrets into a per-tenant vault, add row-level security keyed on the claim, then filter tool visibility and turn on per-tenant audit. Each step is shippable and testable on its own. Test isolation adversarially, replay Tenant A's token against Tenant B's data and confirm it is rejected at the token layer, not merely filtered at the query layer.

Final thoughts

The MCP authorization spec gives you three hard requirements: OAuth 2.1 as the frame, audience-bound tokens through resource indicators, and no token passthrough. Everything else that matters for an enterprise, multi-tenant isolation, per-tool scopes, delegated downstream access, and audit, lives above the spec and is yours to design. Treat the MCP server as a resource server that validates and enforces, keep tenant identity in a verified claim, and log enough to prove isolation held. Do that and an MCP server stops being the weakest link in your agent stack.

Sources and further reading

Security hardening before assurance

Preparing an application for an independent pentest or customer security review? Wavect hardens authorization, secrets, failure paths, and regression coverage, then helps your team close the findings.

Relevant service paths:

Inbox, without the noise

Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

What would you like to receive?
Choose your topics

Free, double opt-in, no tracking pixels.

Back
Kevin Riedl

13 min read · 23 Jul 2026

Next