Model Context Protocol (MCP): Connect AI to Your Tools and DataAuthorization and security · Lesson 11 of 18

Authorization for remote MCP servers: OAuth done right

Article · 18 min · 9 min lecture

Video lecture

Authorization for remote MCP servers: OAuth done right

15 chapters · about 9 min · full transcript

Coming soon

Chapter 1 of 15

Authorization for remote servers

  • Roles and flow
  • Audience and passthrough rules
  • Scopes and least privilege
  • Token verification code

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

When authorization applies

Authorization in MCP is optional and applies to HTTP-based transports. stdio servers should not use the OAuth flow; they take credentials from the environment. For any remote server that touches non-public data, you need it.

The roles

  • The MCP server is an OAuth 2.1 resource server: it accepts and validates access tokens.
  • The authorization server (your identity provider or a dedicated auth service: Microsoft Entra ID, Okta, Auth0, Keycloak, Google, etc.) issues tokens.
  • The MCP client (inside the host) is an OAuth client acting on behalf of the user.

The flow, step by step (2026-07-28)

  1. The client calls the server without a token; the server returns 401 with a WWW-Authenticate header pointing to its Protected Resource Metadata (RFC 9728). Servers must implement this metadata; it names the authorization server(s) and supported scopes.
  2. The client fetches the authorization server's metadata via OAuth 2.0 Authorization Server Metadata (RFC 8414) or OpenID Connect Discovery; clients must support both.
  3. The client obtains a client ID. Preferred: Client ID Metadata Documents (CIMD), where the client ID is an HTTPS URL pointing to a JSON document describing the client (name, redirect URIs). Alternatives: pre-registration, or Dynamic Client Registration (RFC 7591), which 2026-07-28 deprecates in favor of CIMD (still available for compatibility; clients must set an appropriate application_type).
  4. Authorization code flow with PKCE. The client sends the resource parameter (RFC 8707 Resource Indicators) with the MCP server's canonical URI in both the authorization and token requests, so the token is bound to that server.
  5. On the redirect back, the client validates the iss parameter (RFC 9207) against the authorization server it expected before redeeming the code, closing mix-up attacks.
  6. The client calls the MCP server with Authorization: Bearer <token> on every request (never in the query string).
  7. The server validates the token: signature/introspection, expiry, audience (issued for this server), and scopes. Invalid or expired tokens get 401; insufficient scopes get 403 with a scope challenge so clients can do step-up authorization.

Client credentials are bound to the authorization server that issued them: clients must key stored credentials by issuer and re-register if the authorization server changes.

The two rules people break

  • Audience validation: a server must only accept tokens issued for it. A token for another API, even from the same identity provider, must be rejected.
  • No token passthrough: a server must not accept tokens intended for others and must not forward the client's token to downstream APIs. If your server calls a downstream API (say, a CRM), it obtains its own credential for that API (for example via its own OAuth client, a token exchange, or URL-mode elicitation for the user to connect that account). Passthrough creates a confused deputy: downstream services can't tell who is really acting, and audit trails and rate limits break.

Scopes and least privilege

Define scopes around capabilities, for example crm:read, crm:write, campaigns:pause. Map tools to scopes and check them in every handler. Start users with read scopes; request write scopes via step-up only when they try a write tool. Enterprise deployments can use the Enterprise-Managed Authorization extension so the company IdP centrally controls which MCP servers and scopes employees can use; machine-to-machine jobs can use the OAuth client credentials extension.

Worked example: securing a CRM MCP server for a Lahore–London agency

  • Authorization server: the agency's Microsoft Entra ID tenant.
  • Server publishes Protected Resource Metadata listing Entra as the authorization server and scopes crm.read, crm.write.
  • Hosts (Claude connector, VS Code) discover, register via CIMD where supported, and run PKCE flows with resource=https://mcp.agency.example/mcp.
  • The server validates JWT signature, issuer, audience and expiry; maps tools to scopes; returns 403 with scope="crm.write" when an analyst tries crm_update_deal_stage.
  • To call the CRM vendor's API, the server uses its own OAuth client credentials with a service account limited to the agency's workspace, and records the end user's identity in its audit log.

Hands-on: token verification in the Python SDK

import os
import jwt                                     # pip install pyjwt[crypto]
from jwt import PyJWKClient
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings

RESOURCE = os.environ["MCP_RESOURCE_URL"]          # e.g. https://mcp.agency.example/mcp
ISSUER = os.environ["OAUTH_ISSUER"]                # your authorization server's issuer URL
jwks = PyJWKClient(os.environ["OAUTH_JWKS_URL"])

class JwtVerifier(TokenVerifier):
    async def verify_token(self, token: str) -> AccessToken | None:
        try:
            key = jwks.get_signing_key_from_jwt(token).key
            claims = jwt.decode(token, key, algorithms=["RS256"], audience=RESOURCE, issuer=ISSUER)
        except jwt.PyJWTError:
            return None                              # SDK responds 401
        scopes = claims.get("scp", claims.get("scope", "")).split()
        return AccessToken(token=token, client_id=claims.get("azp", claims.get("client_id", "unknown")),
                           scopes=scopes, expires_at=claims.get("exp"), resource=RESOURCE)

mcp = MCPServer(
    "crm",
    token_verifier=JwtVerifier(),
    auth=AuthSettings(issuer_url=AnyHttpUrl(ISSUER), resource_server_url=AnyHttpUrl(RESOURCE),
                      required_scopes=["crm.read"], validate_token_resource=True),
)

The SDK serves the Protected Resource Metadata and enforces the required scopes; inside tools, check finer-grained scopes before writes. Claim names differ between identity providers (for example scp vs scope), and some issue opaque tokens that require introspection instead of local JWT validation: check your provider's documentation. The TypeScript SDK offers equivalent middleware (requireBearerAuth) in its Express package.

Pitfalls

  • Accepting any valid JWT from your IdP without checking audience.
  • Forwarding the user's token to downstream APIs.
  • Putting tokens in URLs or logs.
  • Granting write scopes by default.
  • Skipping iss validation on the client side.

Measuring success

Security tests: tokens for other audiences rejected, expired tokens rejected, scope enforcement per tool, no tokens in logs. Operationally: auth failure rates and step-up prompts per week.

Key takeaways

  • MCP authorization applies to HTTP transports; stdio servers use environment credentials.
  • The MCP server is an OAuth 2.1 resource server publishing Protected Resource Metadata (RFC 9728).
  • Clients use PKCE, resource indicators (RFC 8707) and issuer validation (RFC 9207); CIMD is preferred over deprecated DCR.
  • Servers must validate audience and never pass client tokens through to downstream APIs.
  • Map tools to scopes, start with read scopes, and use step-up for writes.

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. Your MCP server accepts any valid token from the company IdP, including tokens issued for the HR API. What is wrong?
  2. Your server needs to call the CRM vendor's API. How should it authenticate?
  3. Which client registration approach does 2026-07-28 prefer?

Put it into practice

Draw the OAuth flow for your remote server with your identity provider, list scopes per tool, and write three negative security tests (wrong audience, expired token, missing scope).

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.