Exploring OAuth 2.0: Enabling Google Authentication in a Pure Browser-Based Web App running on Cloudflare
Tags: javascript, opensource, cloudflare, webapp
Published: 2024-02-17 03:19:16

As I mentioned [last week](https://dev.to/wanggithub0/exploring-oauth-20-enabling-google-authentication-in-a-pure-browser-based-web-app-2-14mb), our [ChatCraft](https://github.com/tarasglek/chatcraft.org) 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](https://developers.google.com/identity/protocols/oauth2/web-server).

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");
    }
  }
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x89lqo9qaffokzrcwpke.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugc43q7e7ifnrct0qbce.png)


- **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! 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lh4nui348uwmfw3xccfd.png)

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.