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)