Skip to main content
Glama
bighadj22

Cloudflare MCP with Google OAuth and Analytics

by bighadj22
google-handler.ts3.68 kB
import type { AuthRequest, OAuthHelpers } from "@cloudflare/workers-oauth-provider"; import { type Context, Hono } from "hono"; import { fetchUpstreamAuthToken, getUpstreamAuthorizeUrl, type Props } from "./utils"; import { clientIdAlreadyApproved, parseRedirectApproval, renderApprovalDialog, } from "./workers-oauth-utils"; const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>(); app.get("/authorize", async (c) => { const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw); const { clientId } = oauthReqInfo; if (!clientId) { return c.text("Invalid request", 400); } if ( await clientIdAlreadyApproved(c.req.raw, oauthReqInfo.clientId, c.env.COOKIE_ENCRYPTION_KEY) ) { return redirectToGoogle(c, oauthReqInfo); } return renderApprovalDialog(c.req.raw, { client: await c.env.OAUTH_PROVIDER.lookupClient(clientId), server: { description: "This MCP Server is a demo for Google OAuth.", name: "Google OAuth Demo", }, state: { oauthReqInfo }, }); }); app.post("/authorize", async (c) => { const { state, headers } = await parseRedirectApproval(c.req.raw, c.env.COOKIE_ENCRYPTION_KEY); if (!state.oauthReqInfo) { return c.text("Invalid request", 400); } return redirectToGoogle(c, state.oauthReqInfo, headers); }); async function redirectToGoogle( c: Context, oauthReqInfo: AuthRequest, headers: Record<string, string> = {}, ) { return new Response(null, { headers: { ...headers, location: getUpstreamAuthorizeUrl({ clientId: c.env.GOOGLE_CLIENT_ID, hostedDomain: c.env.HOSTED_DOMAIN, redirectUri: new URL("/callback", c.req.raw.url).href, scope: "email profile", state: btoa(JSON.stringify(oauthReqInfo)), upstreamUrl: "https://accounts.google.com/o/oauth2/v2/auth", }), }, status: 302, }); } /** * OAuth Callback Endpoint * * This route handles the callback from Google after user authentication. * It exchanges the temporary code for an access token, then stores some * user metadata & the auth token as part of the 'props' on the token passed * down to the client. It ends by redirecting the client back to _its_ callback URL */ app.get("/callback", async (c) => { // Get the oathReqInfo out of KV const oauthReqInfo = JSON.parse(atob(c.req.query("state") as string)) as AuthRequest; if (!oauthReqInfo.clientId) { return c.text("Invalid state", 400); } // Exchange the code for an access token const code = c.req.query("code"); if (!code) { return c.text("Missing code", 400); } const [accessToken, googleErrResponse] = await fetchUpstreamAuthToken({ clientId: c.env.GOOGLE_CLIENT_ID, clientSecret: c.env.GOOGLE_CLIENT_SECRET, code, grantType: "authorization_code", redirectUri: new URL("/callback", c.req.url).href, upstreamUrl: "https://accounts.google.com/o/oauth2/token", }); if (googleErrResponse) { return googleErrResponse; } // Fetch the user info from Google const userResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", { headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!userResponse.ok) { return c.text(`Failed to fetch user info: ${await userResponse.text()}`, 500); } const { id, name, email } = (await userResponse.json()) as { id: string; name: string; email: string; }; // Return back to the MCP client a new token const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ metadata: { label: name, }, props: { accessToken, email, name, } as Props, request: oauthReqInfo, scope: oauthReqInfo.scope, userId: id, }); return Response.redirect(redirectTo); }); export { app as GoogleHandler };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bighadj22/cloudflare-mcp-google-oauth-analytics'

If you have feedback or need assistance with the MCP directory API, please join our Discord server