Skip to content

Commit 07150ef

Browse files
authored
Merge pull request #28 from irvinebroque/bib/gh-ex
Move GithHub handler to own file
2 parents da23aa4 + 9ea77e1 commit 07150ef

File tree

2 files changed

+86
-84
lines changed

2 files changed

+86
-84
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { AuthRequest, OAuthHelpers } from "@cloudflare/workers-oauth-provider";
2+
import { Hono } from "hono";
3+
import { Octokit } from "octokit";
4+
import { fetchUpstreamAuthToken, getUpstreamAuthorizeUrl } from "./utils";
5+
6+
const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>();
7+
8+
/**
9+
* OAuth Authorization Endpoint
10+
*
11+
* This route initiates the GitHub OAuth flow when a user wants to log in.
12+
* It creates a random state parameter to prevent CSRF attacks and stores the
13+
* original OAuth request information in KV storage for later retrieval.
14+
* Then it redirects the user to GitHub's authorization page with the appropriate
15+
* parameters so the user can authenticate and grant permissions.
16+
*/
17+
app.get("/authorize", async (c) => {
18+
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
19+
if (!oauthReqInfo.clientId) {
20+
return c.text("Invalid request", 400);
21+
}
22+
23+
return Response.redirect(
24+
getUpstreamAuthorizeUrl({
25+
upstream_url: "https://github.com/login/oauth/authorize",
26+
scope: "read:user",
27+
client_id: c.env.GITHUB_CLIENT_ID,
28+
redirect_uri: new URL("/callback", c.req.url).href,
29+
state: btoa(JSON.stringify(oauthReqInfo)),
30+
}),
31+
);
32+
});
33+
34+
/**
35+
* OAuth Callback Endpoint
36+
*
37+
* This route handles the callback from GitHub after user authentication.
38+
* It exchanges the temporary code for an access token, then stores some
39+
* user metadata & the auth token as part of the 'props' on the token passed
40+
* down to the client. It ends by redirecting the client back to _its_ callback URL
41+
*/
42+
app.get("/callback", async (c) => {
43+
// Get the oathReqInfo out of KV
44+
const oauthReqInfo = JSON.parse(atob(c.req.query("state") as string)) as AuthRequest;
45+
if (!oauthReqInfo.clientId) {
46+
return c.text("Invalid state", 400);
47+
}
48+
49+
// Exchange the code for an access token
50+
const [accessToken, errResponse] = await fetchUpstreamAuthToken({
51+
upstream_url: "https://github.com/login/oauth/access_token",
52+
client_id: c.env.GITHUB_CLIENT_ID,
53+
client_secret: c.env.GITHUB_CLIENT_SECRET,
54+
code: c.req.query("code"),
55+
redirect_uri: new URL("/callback", c.req.url).href,
56+
});
57+
if (errResponse) return errResponse;
58+
59+
// Fetch the user info from GitHub
60+
const user = await new Octokit({ auth: accessToken }).rest.users.getAuthenticated();
61+
const { login, name, email } = user.data;
62+
63+
// Return back to the MCP client a new token
64+
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
65+
request: oauthReqInfo,
66+
userId: login,
67+
metadata: {
68+
label: name,
69+
},
70+
scope: oauthReqInfo.scope,
71+
// This will be available on this.props inside MyMCP
72+
props: {
73+
login,
74+
name,
75+
email,
76+
accessToken,
77+
} as Props,
78+
});
79+
80+
return Response.redirect(redirectTo);
81+
});
82+
83+
export const GitHubHandler = app;

demos/remote-mcp-github-oauth/src/index.ts

Lines changed: 3 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import OAuthProvider, {
2-
type AuthRequest,
3-
type OAuthHelpers,
4-
} from "@cloudflare/workers-oauth-provider";
1+
import OAuthProvider from "@cloudflare/workers-oauth-provider";
52
import { DurableMCP } from "workers-mcp";
63
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
74
import { z } from "zod";
8-
import { Hono } from "hono";
95
import { Octokit } from "octokit";
10-
import { fetchUpstreamAuthToken, getUpstreamAuthorizeUrl } from "./utils";
6+
import { GitHubHandler } from "./github-handler";
117

128
// Context from the auth process, encrypted & stored in the auth token
139
// and provided to the DurableMCP as this.props
@@ -92,87 +88,10 @@ export class MyMCP extends DurableMCP<Props, Env> {
9288
}
9389
}
9490

95-
const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>();
96-
97-
/**
98-
* OAuth Authorization Endpoint
99-
*
100-
* This route initiates the GitHub OAuth flow when a user wants to log in.
101-
* It creates a random state parameter to prevent CSRF attacks and stores the
102-
* original OAuth request information in KV storage for later retrieval.
103-
* Then it redirects the user to GitHub's authorization page with the appropriate
104-
* parameters so the user can authenticate and grant permissions.
105-
*/
106-
app.get("/authorize", async (c) => {
107-
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
108-
if (!oauthReqInfo.clientId) {
109-
return c.text("Invalid request", 400);
110-
}
111-
112-
return Response.redirect(
113-
getUpstreamAuthorizeUrl({
114-
upstream_url: "https://github.com/login/oauth/authorize",
115-
scope: "read:user",
116-
client_id: c.env.GITHUB_CLIENT_ID,
117-
redirect_uri: new URL("/callback", c.req.url).href,
118-
state: btoa(JSON.stringify(oauthReqInfo)),
119-
}),
120-
);
121-
});
122-
123-
/**
124-
* OAuth Callback Endpoint
125-
*
126-
* This route handles the callback from GitHub after user authentication.
127-
* It exchanges the temporary code for an access token, then stores some
128-
* user metadata & the auth token as part of the 'props' on the token passed
129-
* down to the client. It ends by redirecting the client back to _its_ callback URL
130-
*/
131-
app.get("/callback", async (c) => {
132-
// Get the oathReqInfo out of KV
133-
const oauthReqInfo = JSON.parse(atob(c.req.query("state") as string)) as AuthRequest;
134-
if (!oauthReqInfo.clientId) {
135-
return c.text("Invalid state", 400);
136-
}
137-
138-
// Exchange the code for an access token
139-
const [accessToken, errResponse] = await fetchUpstreamAuthToken({
140-
upstream_url: "https://github.com/login/oauth/access_token",
141-
client_id: c.env.GITHUB_CLIENT_ID,
142-
client_secret: c.env.GITHUB_CLIENT_SECRET,
143-
code: c.req.query("code"),
144-
redirect_uri: new URL("/callback", c.req.url).href,
145-
});
146-
if (errResponse) return errResponse;
147-
148-
// Fetch the user info from GitHub
149-
const user = await new Octokit({ auth: accessToken }).rest.users.getAuthenticated();
150-
const { login, name, email } = user.data;
151-
152-
// Return back to the MCP client a new token
153-
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
154-
request: oauthReqInfo,
155-
userId: login,
156-
metadata: {
157-
label: name,
158-
},
159-
scope: oauthReqInfo.scope,
160-
// This will be available on this.props inside MyMCP
161-
props: {
162-
login,
163-
name,
164-
email,
165-
accessToken,
166-
} as Props,
167-
});
168-
169-
return Response.redirect(redirectTo);
170-
});
171-
17291
export default new OAuthProvider({
17392
apiRoute: "/sse",
17493
apiHandler: MyMCP.mount("/sse"),
175-
defaultHandler: app,
94+
defaultHandler: GitHubHandler,
17695
authorizeEndpoint: "/authorize",
17796
tokenEndpoint: "/token",
17897
clientRegistrationEndpoint: "/register",

0 commit comments

Comments
 (0)