As I mentioned last week, our ChatCraft run on cloudflare function, which is quite similar to node.js, so I tried to do the Google OAuth using Using OAuth 2.0 for Web Server Applications.
There are four steps:
Step 1: Redirect to Google's OAuth 2.0 server
Step 2: Google prompts user for consent
Step 3: Handle the OAuth 2.0 server response - get code
Step 4: Calling Google APIs - get user info using access_token
- Step1, Step2 The fist 2 steps are quiet similar to what I did before. In order to get the provider, I also added it in state, and parse the state later.
const url = buildUrl( "https://accounts.google.com/o/oauth2/v2/auth", // If there's a chatId, piggy-back it on the request as state chatId ? { client_id: GOOGLE_CLIENT_ID, redirect_uri: GOOGLE_REDIRECT_URI, response_type: GOOGLE_RESPONSE_TYPE, scope: GOOGLE_SCOPE, state: "provider=google&chat_id=" + chatId, } : { client_id: GOOGLE_CLIENT_ID, redirect_uri: GOOGLE_REDIRECT_URI, response_type: GOOGLE_RESPONSE_TYPE, scope: GOOGLE_SCOPE, state: "provider=google", } ); return Response.redirect(url, 302);
At first, I run the login with a provider /api/login?provider=${provider}&chat_id=${chatId}
, so I can get the provider directly using reqUrl.searchParams.get("provider")
then it directed to Google, after user consent, it redirect back with state state=provider%3Dgoogle%26chat_id%3Dl77...
, so I used get state and then decodeURI+get provider:
Here is how I parse the state.
let provider = reqUrl.searchParams.get("provider"); if (!provider) { let state = reqUrl.searchParams.get("state"); if (state) { state = decodeURIComponent(state); const stateParams = new URLSearchParams(state); provider = stateParams.get("provider"); } } //state=provider%3Dgoogle%26chat_id%3Dl77... let chatId = reqUrl.searchParams.get("chat_id"); if (!chatId) { let state = reqUrl.searchParams.get("state"); if (state) { state = decodeURIComponent(state); const stateParams = new URLSearchParams(state); chatId = stateParams.get("chatId"); } }
- Step3 This step I tried to test using Postman firstly, then I got the code.
export async function requestGoogleAccessToken( code: string, CLIENT_ID: string, CLIENT_SECRET: string, GOOGLE_REDIRECT_URI: string ) { const url = buildUrl("https://accounts.google.com/o/oauth2/token", { code: code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: GOOGLE_REDIRECT_URI, grant_type: "authorization_code", }); const res = await fetch(url, { method: "POST", headers: { "User-Agent": "chatcraft.org", }, }); if (!res.ok) { throw new Error(`Failed to get Google token: ${res.status} ${await res.text()}`); } const result = (await res.json()) as { error?: string; access_token: string; }; if (result.error) { throw new Error(`Error in Google token response: ${result.error}`); } return result.access_token; }
- Step4 This step I also tried to test using Postman firstly, then I got the user information.
export async function requestGoogleUserInfo(token: string): Promise<User> { const res = await fetch("https://www.googleapis.com/oauth2/v1/userinfo", { headers: { Accept: "application/json", Authorization: `Bearer ${token}`, "User-Agent": "chatcraft.org", }, }); if (!res.ok) { throw new Error(`Failed to get Google User info: ${res.status} ${await res.text()}`); } const { email, name, picture } = (await res.json()) as { email: string; name: string; picture: string; }; return { username: email, name: name, avatarUrl: picture }; }
Finally, I made it!
I have been testing with my own account locally. However, when I deployed to the production environment, I noticed that the Google environment parameters are all undefined. After connecting with my professor, I found the environment variables name with my variables are different. After revising it, it threw "Error 400: redirect_uri_mismatch". Later we will check the redirect uri set in Google Authorized redirect URIs.
Top comments (0)