Back to blog

OAuth for MCP explained with a real-world example

Auth & identity

Jun 11, 2025

Author: Robert Fenstermacher

OAuth for MCP explained with a real-world example

When AI agents interact with your application on behalf of users, the underlying infrastructure can quickly become complicated. Model Context Protocol (MCP) simplifies most of this integration work by standardizing how AI systems, like LLM-based agents, communicate with external services.

More recently, MCP introduced changes to accommodate deployments that span networks and organizations and to manage user-consented access securely. Part of these updates aligned MCP with the OAuth framework (the same framework behind “Sign in with Google”). Now, when an AI agent (the MCP client) requests protected resources from an MCP server, it uses OAuth to obtain explicit user consent.

Live demo

Want the TL;DR? Watch Max Gerber spin up an OAuth-secured MCP server in this live demo before we break down exactly how it works step by step.

--> Watch the video here

Starting with OAuth fundamentals

Before diving into MCP specifics, let's recap the basics of OAuth 2.0 (more precisely the OAuth 2.1 draft, which builds on 2.0). OAuth is an authorization framework that allows an app to access resources on behalf of a user, without the user sharing their credentials (like passwords) with that app.

Here’s a quick summary of OAuth concepts:

Actors

  • Resource Owner (user): The person granting access to their data
  • Client (AI agent): The MCP client requesting access.
  • Resource Server (MCP server): The backend validating‬‭ access and serving data.‬
  • Authorization Server: Authenticates users and issues‬‭ tokens (can be standalone or‬ part of the MCP server).‬

Credentials & flow mechanics

  • ‬‭Authorization Code: A short-lived credential used in the OAuth authorization code flow. It represents the user's approval and is exchanged for tokens.
  • PKCE (Proof Key for Code Exchange): An OAuth extension that secures public clients by binding the authorization code to the client. Mandated in OAuth 2.1.
  • Access Token: A short-lived credential (often a JWT) used to prove permissions when calling APIs.

Together, these elements let apps securely perform actions for users without ever seeing sensitive credentials directly.

Why OAuth matters for MCP

Now, how do these OAuth concepts map to MCP? The Model Context Protocol is essentially a standard interface for connecting AI agents to tools or data sources. Initially, MCP assumed a trusted environment – for example, a local AI agent connecting to a local database or a server under the same control. In such cases, authentication could be rudimentary or even skipped.

However, as MCP gained traction, new use cases emerged: what if the MCP server (the tool or data source) is a cloud service or third-party API running remotely, and the MCP client (the agent) is running on a user's device or in another cloud? In those situations, the server and client need a robust external authorization framework—enter OAuth.

OAuth flow in the MCP context

Let's walk through how the OAuth authorization flow works in the context of MCP, where an AI agent gets permission to call an MCP server on behalf of a user.

1. Initial request → 401

The agent calls a protected endpoint (GET /todos) without a token. The MCP server replies 401 Unauthorized (optionally with a WWW-Authenticate: Bearer hint). This tells the agent to begin an OAuth exchange.

2. Discover auth endpoints

Using RFC 8414 metadata, the agent downloads /.well-known/oauth-authorization-server (or falls back to /authorize, /token, /register). This JSON advertises:

  • authorization_endpoint – where to send users
  • token_endpoint – where to swap codes for tokens
  • registration_endpoint – for dynamic client registration (optional)
  • supported scopes, PKCE methods, and client-auth options

Spec in motion: This discovery step is one of the more fluid areas in the MCP spec. As interoperability needs grow, future versions may adjust how authorization servers and identity providers are discovered or linked, especially in deployments spanning multiple systems. But while the format may evolve, the core flow will remain stable.

3. Redirect user for login & consent

The client opens the user’s browser to the authorization URL:

https://auth.example.com/authorize
  ?response_type=code
  &client_id=client123
  &redirect_uri=https://agent.app/callback
  &scope=todos.read todos.write
  &code_challenge=<hash>
  &code_challenge_method=S256
  &state=<random>

The user signs in (if needed) and sees a consent screen such as “AI Todo Agent wants to read and add tasks.” Clicking Allow sends the browser back to redirect_uri with ?code=xyz&state=<same>.

4. Exchange code for tokens (PKCE)

The agent now POSTs to the token endpoint:

POST https://auth.example.com/token
grant_type=authorization_code
code=xyz
redirect_uri=https://agent.app/callback
client_id=client123
code_verifier=<original_secret>

5. Call MCP with the token

The agent retries the original API call, adding:

Authorization: Bearer <JWT>

The MCP server validates the JWT signature via its JWKS, checks issuer, audience, expiry, and scopes. If everything matches, it serves the request; otherwise it returns 403 Forbidden.

6. Built-in safeguards

  • PKCE binds the code to the client, blocking interception.
  • Short token lifetimes and rotating refresh tokens limit damage from leaks.
  • Users can revoke access at any time; the agent then fails to refresh and must request consent again..

This flow turns “Here’s my API key” into a user-approved, time-boxed permission—exactly what’s needed when AI agents act on our behalf.

Implementing OAuth in MCP Servers

Implementing OAuth 2.0 properly is not a trivial task – there’s a reason many companies use third-party identity services. As an MCP server developer, you have a couple of approaches to add OAuth support:

Options 1: Roll your own auth server

You can embed a full OAuth provider inside your MCP server, but be ready for a long checklist: create /authorize, /token, and (optionally) /register endpoints; design login and consent screens; store client apps securely; implement PKCE, JWKS rotation, scope checks, and CSRF protection; and keep the whole thing patched and user-friendly. For a small internal tool this might be fine, yet it quickly turns into a major engineering project once multiple clients, refresh tokens, or dynamic registration enter the picture.

Option 2: Delegate to an OAuth platform

A hosted identity platform lets your app behave like an OAuth/OIDC provider without building the infrastructure yourself. A platform like Stytch Connected Apps handles the entire authentication and authorization-side, including:

  • End-to-end OAuth & token management: Authorization-code handling, JWT issuance/verification via JWKS, refresh and revocation—all out of the box.
  • Consent lifecycle & dynamic registration: Built-in consent UX, client tracking, user/admin revocation, plus OAuth DCR so new AI agents self-register.
  • Standards & developer experience: PKCE, redirect-URI validation, scope enforcement, and SDK/UI components (e.g., <IdentityProvider/>) implemented to best-practice specs.

Example implementation

Let’s look at a real example: a simple to-do app that AI agents can access to help users manage tasks. It uses a React frontend, a Cloudflare Worker backend for the MCP server, and Stytch Connected Apps for OAuth. The full stack is based on an open-source demo.

Frontend (React application & OAuth consent)

The React frontend serves as both the UI for the to-do list and the interface for OAuth consent. We used Stytch’s JavaScript SDK to handle user auth and embed the OAuth UI. Specifically, we mounted the <IdentityProvider /> component at the /oauth/authorize route, wrapped in a withLoginRequired guard. This component reads OAuth query parameters (client_id, redirect_uri, scope, code_challenge, etc.) from the URL and renders a consent screen showing the requesting agent and scopes. If the user isn’t logged in, it prompts for login first.

Behind the scenes, Connected Apps manages several key steps. When a new AI agent (MCP client) initiates authorization and isn’t already registered, it performs dynamic client registration by calling Stytch’s /oauth2/register endpoint. This process is encouraged by the MCP spec and happens seamlessly from the client’s perspective—the user simply sees the agent’s name and requested scopes on the consent screen. Registered clients, like “AI Todo Agent,” then appear in the Stytch dashboard with their associated scopes and client IDs.

After the user approves the request, Stytch issues an authorization code and redirects the browser to the agent’s callback URI (e.g., https://mcp-client-app.com/callback). At this point, the frontend’s role is complete—the user has granted access, and the OAuth flow continues between the client and the token endpoint.

Token exchange and MCP Server (Cloudflare Worker)

Once the user approves access, the MCP client sends a request to the token endpoint—Stytch's, in this case—with the authorization code, PKCE code verifier, and other required parameters. Stytch responds with a signed JWT access token (and optionally a refresh token), including claims like user ID, scopes, expiration, issuer, and client ID. A sample payload might look like:

{
  "sub": "<user_id>",
  "scope": "todos.read todos.write",
  "iss": "stytch.com/<project_id>",
  "aud": "<project_id>",
  "exp": <time>
}

With the token in hand, the agent calls our MCP server hosted on Cloudflare Workers. The server supports both regular REST calls and MCP-specific agent requests. We added middleware to validate the Authorization: Bearer header, using Stytch’s JWKS (retrieved and cached via the JOSE library) to verify the token’s signature and claims.

Once validated, the middleware attaches token claims—like user_id and scope—to the request context. That allows our backend to enforce access control: for instance, filtering task data by user ID, or rejecting unauthorized actions. If an agent with only todos.read tries to create a task, the server responds with a 403 and an insufficient_scope error.

After authorization, the server completes the action—returning task data or creating a new one—and the agent proceeds until the token expires. If a refresh token was issued, the agent can silently obtain a new access token without user interaction. Our implementation supports refresh tokens via Stytch, making long-lived agent access seamless.

OAuth metadata & configuration

To wire the pieces together we needed just four setup steps—two in the Stytch dashboard and two in our Cloudflare Worker:

  • Enable Connected Apps + Dynamic RegistrationIn the Stytch project we flipped on Connected Apps and Dynamic Client Registration. Now any new MCP agent can register itself automatically the first time it requests authorization—no manual client setup.
  • Define ScopesWe added todos.read and todos.write in the dashboard, each with a clear, human-friendly description. Those labels appear verbatim on the consent screen so users know exactly what the agent is asking for.
  • Expose OAuth MetadataThe Worker serves a /.well-known/oauth-authorization-server JSON doc that points clients to the right endpoints
{
  "authorization_endpoint": "https://app.example.com/oauth/authorize",
  "token_endpoint":        "https://auth.stytch.com/oauth2/token",
  "registration_endpoint": "https://auth.stytch.com/oauth2/register",
  "scopes_supported":      ["todos.read", "todos.write"],
  "code_challenge_methods_supported": ["S256"]
}

This single file lets any MCP client discover the full flow without hard-coded URLs.

  • Configure Worker Environment We injected the Stytch project ID and JWKS URL as environment vars. At runtime, our token-validation middleware fetches (and caches) the JWKS, checks the JWT’s signature, issuer, audience, expiry, and scopes, then attaches those claims to the request context.

Revocation note: If a user later disconnects an agent, Stytch marks its refresh token invalid. Because our access tokens are short-lived, the agent quietly loses access once its current token expires—no extra work on the Worker side.

That’s all it takes: four quick configs and the OAuth plumbing is in place, self-discoverable, and easy to manage.

Recap of OAuth-enabled MCP flow

Let's recap the journey of an OAuth-enabled MCP interaction, end-to-end:

  1. Agent initiates a request to an MCP server for a user’s protected resource. The server requires a token, so the agent triggers an OAuth flow.
  2. User is redirected to an authorization server (or integrated auth page) where they log in (if needed) and see a consent prompt describing what access the agent is requesting.
  3. User approves the request, granting the agent a set of scopes. The authorization server issues an authorization code and redirects the user back to the agent.
  4. Agent exchanges the code for tokens (access token (JWT) and possibly refresh token) from the token endpoint, using PKCE to secure the exchange.
  5. Agent calls the MCP server with the access token. The MCP server verifies the token (signature, expiration, scopes, etc.) and, if valid, serves the request.
  6. Subsequent requests from the agent include the token and are allowed as long as the token is valid. If the token expires, the agent can use a refresh token (if provided) to get a new one without user involvement.
  7. At any point, the user can revoke access via the authorization server (for instance, disconnect the app in their account settings). This will invalidate tokens going forward, ensuring the agent can no longer access the resource.
  8. Throughout the process, neither the agent nor the MCP server ever saw the user’s password or long-term credentials – all they handled were tokens. The user’s trust in the system is reinforced by being in control: they explicitly granted and can revoke the agent’s access.

From the AI agent developer’s perspective, OAuth adds a few extra steps initially (handling 401s, launching a browser for user consent, storing tokens), but it results in a much safer and more robust integration. From the MCP server developer’s perspective, OAuth support means the server can open up to third-party agents without fear of unauthorized data leaks – every request comes with a verifiable proof of access rights.

Benefits of the OAuth approach

OAuth might seem like a lot to set up, but it brings major advantages to MCP integrations:

Security → PKCE, short-lived tokens, and refresh rotation reduce risk. Users never share passwords; tokens can expire or be revoked easily.

User Consent Users see exactly what access an agent wants and can revoke it anytime. Builds transparency and trust.

Granular Access Scopes let you limit access precisely (e.g., read-only vs. write). Much better than all-or-nothing API keys.

Revocation & Visibility Users/admins can manage and revoke agent access without rotating secrets. Ideal for audits and compliance.

Standards-Based OAuth is widely adopted and battle-tested. MCP benefits from existing tooling, libraries, and security best practices.

Faster Integration Services like Stytch (or AWS Cognito, Auth0, etc.) handle OAuth flows, so you can skip building auth from scratch.

Web API Consistency → Aligns with APIs agents already use (Google, Microsoft, etc.), making it easier for devs and more familiar for users.

In essence, OAuth brings a robust permissioning framework to AI agents, giving all parties (users, agents, and service providers) confidence that access is managed and auditable. It's a proven model that significantly improves both security and user control.

Conclusion

The rise of AI agents connecting to user services via MCP is powerful—but it needs a secure, consent-driven foundation. OAuth 2.0 provides that by letting agents act only within user-approved limits, with access that’s scoped, revocable, and transparent. It replaces risky shortcuts like API keys with a structured, governed flow.

We covered how OAuth works in general and how it fits into MCP, with a real-world example using a modern OAuth provider. The big idea: you don’t need to build your own auth layer. With standards like OAuth built into the MCP spec, and services like Stytch available, you can handle auth cleanly and securely from day one.

Tying AI agents to OAuth brings the familiar "log in with consent" model to AI integrations. It helps users trust agents with their data and helps developers ship powerful features without cutting corners on security. As tooling improves, OAuth for MCP will only get easier. Build it in now—and future-proof your integration.

Share this article