DEV Community

WangGithub0
WangGithub0

Posted on

Exploring OAuth 2.0: Enabling Google Authentication in a Pure Browser-Based Web App running on Cloudflare

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);
Enter fullscreen mode Exit fullscreen mode

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");
    }
  }
Enter fullscreen mode Exit fullscreen mode

Image description

Image description

  • 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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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 };
}
Enter fullscreen mode Exit fullscreen mode

Finally, I made it!

Image description

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)