Skip to content

Commit 5d39540

Browse files
committed
Scaffolding out authorization docs
1 parent 3956dc9 commit 5d39540

File tree

5 files changed

+343
-11
lines changed

5 files changed

+343
-11
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
pcx_content_type: concept
3-
title: External oAuth Provider
3+
title: Use your own OAuth Provider
44
sidebar:
5-
order: 2
5+
order: 4
66
---
77

88
import { Render } from "~/components";

src/content/docs/agents/model-context-protocol/mcp-server/authorization/index.mdx

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,139 @@ sidebar:
88

99
import { DirectoryListing } from "~/components";
1010

11-
When building an MCP (Model Context Protocol) server, you need both a way to allow users to login (authentication) and allow them to grant the MCP client access to resources on their account (authorization).
11+
When building a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server, you need both a way to allow users to login (authentication) and allow them to grant the MCP client access to resources on their account (authorization).
1212

1313
<diagram>
1414

1515
</diagram>
1616

1717
The Model Context Protocol uses [a subset of OAuth 2.1 for authorization](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/). OAuth allows your users to grant limited access to resources, without them having to share API keys or other credentials.
1818

19-
Cloudflare provides an oAuth SDK that implements the provider side of the OAuth 2.1 protocol, allowing you to easily add authorization to your MCP server. You can also use your own OAuth provider with the MCP Server SDK, including Stytch, Auth0, and other authorization providers.
19+
Cloudflare provides an OAuth SDK that implements the provider side of the OAuth 2.1 protocol, allowing you to easily add authorization to your MCP server.
2020

21-
### Workers OAuth Provider SDK
21+
You can use the OAuth SDK in three ways:
22+
23+
1. **Your Worker handles authorization itself.** Your MCP server, running on Cloudflare, handles the complete OAuth flow. ([Example](/agents/model-context-protocol/mcp-server/getting-started/))
24+
2. **Integrate with a third-party OAuth provider**, such as GitHub or Google. ([Example](/agents/model-context-protocol/mcp-server/examples/third-party-oauth-provider/))
25+
3. **Integrate with your own OAuth provider**, including authorization-as-a-service providers such as Stytch and Auth0. ([Example](/agents/model-context-protocol/mcp-server/examples/external-oauth-provider/))
26+
27+
The following sections describe each of these options and link to runnable code examples for each.
28+
29+
## Authorization options
30+
31+
### (1) Your MCP Server handles authorization itself
32+
33+
Your MCP Server, using the Cloudflare MCP Server and OAuth Provider SDKs, can handle the complete OAuth authorization flow, without any third-party involvement.
34+
35+
The [Workers OAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-sdk/) is a Cloudflare Worker that implements a [`fetch()` handler](/workers/runtime-apis/handlers/fetch/), and handles incoming requests to your MCP server. You provide your own handlers for your MCP Server's API, and autentication and authorization logic, and URI paths for the OAuth endpoints, and the SDK handles the rest.
36+
37+
{/* TODO: Update link */}
38+
The OAuth Provider SDK comes with an [example handler implementation](https://github.com/geelen/mcp-remote-examples/tree/main/02-user-password/src/routes) for autentication and authorization, referenced below as `defaultHandler`:
39+
40+
{/* TODO: GithubCodeComponent */}
41+
42+
```ts
43+
import OAuthProvider from "workers-oauth-provider";
44+
45+
// TODO: Bunch of naming decisions here
46+
export default new OAuthProvider({
47+
apiRoute: "/mcp",
48+
// Your MCP server:
49+
apiHandler: MyMCPServer.Router,
50+
// Your handler for authentication and authorization:
51+
defaultHandler: OAuthProvider.defaultHandler,
52+
// TODO: Should these have default values?
53+
authorizeEndpoint: "/authorize",
54+
tokenEndpoint: "/token",
55+
clientRegistrationEndpoint: "/register",
56+
});
57+
```
58+
59+
```mermaid
60+
sequenceDiagram
61+
participant B as User-Agent (Browser)
62+
participant C as MCP Client
63+
participant M as MCP Server (your Worker)
64+
65+
C->>M: MCP Request
66+
M->>C: HTTP 401 Unauthorized
67+
Note over C: Generate code_verifier and code_challenge
68+
C->>B: Open browser with authorization URL + code_challenge
69+
B->>M: GET /authorize
70+
Note over M: User logs in and authorizes
71+
M->>B: Redirect to callback URL with auth code
72+
B->>C: Callback with authorization code
73+
C->>M: Token Request with code + code_verifier
74+
M->>C: Access Token (+ Refresh Token)
75+
C->>M: MCP Request with Access Token
76+
Note over C,M: Begin standard MCP message exchange
77+
```
78+
79+
Remember — [authentication is different from authorization](https://www.cloudflare.com/learning/access-management/authn-vs-authz/). Your MCP Server can handle authorization itself, while still relying on an external authentication service to first authenticate users. The [example](/agents/model-context-protocol/mcp-server/) in getting started provides a mock authentdcation flow. You will need to implement your own authentication handler — either handling authentication yourself, or using an external authentication service such as Clerk, Stytch, Auth0 or others.
80+
81+
For a step-by-step example, refer to the [Worker as OAuth Provider](/agents/model-context-protocol/mcp-server/authorization/worker-as-oauth-provider/) section., and refer to the [API reference docs for the OAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-sdk/).
82+
83+
### (2) Third-party OAuth Provider
84+
85+
The OAuth Provider SDK can be configured to use a third-party OAuth provider, such as [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) or [Google](https://developers.google.com/identity/protocols/oauth2).
86+
87+
When you use a third-party OAuth provider, you must provide a handler to the `OAuthProvider` that implements the OAuth flow for the third-party provider.
2288

2389
```ts
2490
import OAuthProvider from "workers-oauth-provider";
91+
import MyAuthHandler from "./auth-handler";
2592

93+
// TODO: Bunch of naming decisions here
2694
export default new OAuthProvider({
2795
apiRoute: "/mcp",
96+
// Your MCP server:
2897
apiHandler: MyMCPServer.Router,
29-
defaultHandler: MyMCPServer.defaultHandler,
98+
// Your handler for authentication and authorization with the third-party provider:
99+
defaultHandler: MyAuthHandler,
100+
// TODO: Should these have default values?
30101
authorizeEndpoint: "/authorize",
31102
tokenEndpoint: "/token",
32103
clientRegistrationEndpoint: "/register",
33104
});
34105
```
35106

107+
Note that as [defined in the Model Context Protocol specification](https://spec.modelcontextprotocol.io/specification/draft/basic/authorization/#292-flow-description) when you use a third-party OAuth provider, the MCP Server (your Worker) generates and issues its own token to the MCP client:
108+
109+
```mermaid
110+
sequenceDiagram
111+
participant B as User-Agent (Browser)
112+
participant C as MCP Client
113+
participant M as MCP Server (your Worker)
114+
participant T as Third-Party Auth Server
115+
116+
C->>M: Initial OAuth Request
117+
M->>B: Redirect to Third-Party /authorize
118+
B->>T: Authorization Request
119+
Note over T: User authorizes
120+
T->>B: Redirect to MCP Server callback
121+
B->>M: Authorization code
122+
M->>T: Exchange code for token
123+
T->>M: Third-party access token
124+
Note over M: Generate bound MCP token
125+
M->>B: Redirect to MCP Client callback
126+
B->>C: MCP authorization code
127+
C->>M: Exchange code for token
128+
M->>C: MCP access token
129+
```
130+
36131
Read the docs for the [Workers oAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-sdk/) for more details.
37132

38-
### External OAuth Providers
133+
### (3) Bring your own OAuth Provider
39134

40-
You can also use an external OAuth provider with the MCP Server SDK.
135+
If your application already implements an Oauth Provider itself, or you use Stytch, Auth0, or authorization-as-a-service provider, you can use this in the same way that you would use a third-party OAuth provider, described above.
136+
137+
The following examples show how to use the OAuth Provider SDK with an external OAuth provider:
41138

42139
- [Stytch](/agents/model-context-protocol/mcp-server/examples/stytch/)
140+
- [Auth0](/agents/model-context-protocol/mcp-server/examples/auth0/)
141+
142+
## Next steps
43143

44-
<DirectoryListing />
144+
- [Learn how to use the OAuth Provider SDK](/agents/model-context-protocol/mcp-server/authorization/oauth-sdk/)
145+
- [Learn how to use a third-party OAuth provider](/agents/model-context-protocol/mcp-server/examples/third-party-oauth-provider/)
146+
- [Learn how to bring your own OAuth provider](/agents/model-context-protocol/mcp-server/examples/external-oauth-provider/)

src/content/docs/agents/model-context-protocol/mcp-server/authorization/oauth-sdk.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
pcx_content_type: concept
3-
title: Workers oAuth SDK
3+
title: API reference
44
sidebar:
5-
order: 1
5+
order: 5
66
---
77

88
import { Render } from "~/components";
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
pcx_content_type: concept
3+
title: Third-party OAuth Provider
4+
sidebar:
5+
order: 3
6+
---
7+
8+
import { Render, GitHubCode } from "~/components";
9+
10+
{/* TODO: Why doesn't GitHub Code Component work? */}
11+
12+
```ts
13+
import OAuthProvider, {
14+
AuthRequest,
15+
OAuthHelpers,
16+
} from "workers-oauth-provider";
17+
import { MCPEntrypoint } from "./lib/MCPEntrypoint";
18+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19+
import { z } from "zod";
20+
import { Hono } from "hono";
21+
import pick from "just-pick";
22+
import { Octokit } from "octokit";
23+
24+
// Context from the auth process, encrypted & stored in the auth token
25+
// and provided to the MCP Server as this.props
26+
type Props = {
27+
login: string;
28+
name: string;
29+
email: string;
30+
accessToken: string;
31+
};
32+
33+
export class MyMCP extends MCPEntrypoint<Props> {
34+
get server() {
35+
const server = new McpServer({
36+
name: "Github OAuth Proxy Demo",
37+
version: "1.0.0",
38+
});
39+
40+
server.tool(
41+
"add",
42+
"Add two numbers the way only MCP can",
43+
{ a: z.number(), b: z.number() },
44+
async ({ a, b }) => ({
45+
content: [{ type: "text", text: String(a + b) }],
46+
}),
47+
);
48+
49+
server.tool(
50+
"whoami",
51+
"Tasty props from my OAuth provider",
52+
{},
53+
async () => ({
54+
content: [
55+
{
56+
type: "text",
57+
text: JSON.stringify(pick(this.props, "login", "name", "email")),
58+
},
59+
],
60+
}),
61+
);
62+
63+
server.tool(
64+
"userInfoHTTP",
65+
"Get user info from GitHub, via HTTP",
66+
{},
67+
async () => {
68+
const res = await fetch("https://api.github.com/user", {
69+
headers: {
70+
Authorization: `Bearer ${this.props.accessToken}`,
71+
"User-Agent": "04-auth-pivot",
72+
},
73+
});
74+
return { content: [{ type: "text", text: await res.text() }] };
75+
},
76+
);
77+
78+
server.tool(
79+
"userInfoOctokit",
80+
"Get user info from GitHub, via Octokit",
81+
{},
82+
async () => {
83+
const octokit = new Octokit({ auth: this.props.accessToken });
84+
return {
85+
content: [
86+
{
87+
type: "text",
88+
text: JSON.stringify(await octokit.rest.users.getAuthenticated()),
89+
},
90+
],
91+
};
92+
},
93+
);
94+
return server;
95+
}
96+
}
97+
98+
const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>();
99+
100+
/**
101+
* OAuth Authorization Endpoint
102+
*
103+
* This route initiates the GitHub OAuth flow when a user wants to log in.
104+
* It creates a random state parameter to prevent CSRF attacks and stores the
105+
* original OAuth request information in KV storage for later retrieval.
106+
* Then it redirects the user to GitHub's authorization page with the appropriate
107+
* parameters so the user can authenticate and grant permissions.
108+
*/
109+
app.get("/authorize", async (c) => {
110+
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
111+
// Store the request info in KV to catch ya up on the rebound
112+
const randomString = crypto.randomUUID();
113+
await c.env.OAUTH_KV.put(
114+
`login:${randomString}`,
115+
JSON.stringify(oauthReqInfo),
116+
{ expirationTtl: 600 },
117+
);
118+
119+
const upstream = new URL(`https://github.com/login/oauth/authorize`);
120+
upstream.searchParams.set("client_id", c.env.GITHUB_CLIENT_ID);
121+
upstream.searchParams.set(
122+
"redirect_uri",
123+
new URL("/callback", c.req.url).href,
124+
);
125+
upstream.searchParams.set("scope", "read:user");
126+
upstream.searchParams.set("state", randomString);
127+
upstream.searchParams.set("response_type", "code");
128+
129+
return Response.redirect(upstream.href);
130+
});
131+
132+
/**
133+
* OAuth Callback Endpoint
134+
*
135+
* This route handles the callback from GitHub after user authentication.
136+
* It exchanges the temporary code for an access token, then stores some
137+
* user metadata & the auth token as part of the 'props' on the token passed
138+
* down to the client. It ends by redirecting the client back to _its_ callback URL
139+
*/
140+
app.get("/callback", async (c) => {
141+
const code = c.req.query("code") as string;
142+
143+
// Get the oathReqInfo out of KV
144+
const randomString = c.req.query("state");
145+
if (!randomString) {
146+
return c.text("Missing state", 400);
147+
}
148+
const oauthReqInfo = await c.env.OAUTH_KV.get<AuthRequest>(
149+
`login:${randomString}`,
150+
{ type: "json" },
151+
);
152+
if (!oauthReqInfo) {
153+
return c.text("Invalid state", 400);
154+
}
155+
156+
// Exchange the code for an access token
157+
const resp = await fetch(`https://github.com/login/oauth/access_token`, {
158+
method: "POST",
159+
headers: {
160+
"Content-Type": "application/x-www-form-urlencoded",
161+
},
162+
body: new URLSearchParams({
163+
client_id: c.env.GITHUB_CLIENT_ID,
164+
client_secret: c.env.GITHUB_CLIENT_SECRET,
165+
code,
166+
redirect_uri: new URL("/callback", c.req.url).href,
167+
}).toString(),
168+
});
169+
if (!resp.ok) {
170+
console.log(await resp.text());
171+
return c.text("Failed to fetch access token", 500);
172+
}
173+
const body = await resp.formData();
174+
const accessToken = body.get("access_token");
175+
if (!accessToken) {
176+
return c.text("Missing access token", 400);
177+
}
178+
179+
// Fetch the user info from GitHub
180+
const apiRes = await fetch(`https://api.github.com/user`, {
181+
headers: {
182+
Authorization: `bearer ${accessToken}`,
183+
"User-Agent": "04-auth-pivot",
184+
},
185+
});
186+
if (!apiRes.ok) {
187+
console.log(await apiRes.text());
188+
return c.text("Failed to fetch user", 500);
189+
}
190+
191+
const user = (await apiRes.json()) as Record<string, string>;
192+
const { login, name, email } = user;
193+
194+
// Return back to the MCP client a new token
195+
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
196+
request: oauthReqInfo,
197+
userId: login,
198+
metadata: {
199+
label: name,
200+
},
201+
scope: oauthReqInfo.scope,
202+
// This will be available on this.props inside MyMCP
203+
props: {
204+
login,
205+
name,
206+
email,
207+
accessToken,
208+
} as Props,
209+
});
210+
211+
return Response.redirect(redirectTo);
212+
});
213+
214+
export default new OAuthProvider({
215+
apiRoute: "/sse",
216+
apiHandler: MyMCP.Router,
217+
defaultHandler: app,
218+
authorizeEndpoint: "/authorize",
219+
tokenEndpoint: "/token",
220+
clientRegistrationEndpoint: "/register",
221+
});
222+
```

0 commit comments

Comments
 (0)