How to setup Google OAuth2 Login

Hi,

I am trying to create a simple example to perform OAuth and 3rd Party Authentication using Google using React for my Client Application. While reading the documentation, I realized that there is no available API that allows me to do that in a single shot.

According to the documentation, Parse allows you to link your users with 3rd party authentication, enabling your users to sign up or log into your application using their existing identities. While reading other part of the documentation, the format for authData is the following

    {
      "google": {
        "id": "user's Google id (string)",
        "id_token": "an authorized Google id_token for the user (use when not using access_token)",
        "access_token": "an authorized Google access_token for the user (use when not using id_token)"
      }
    }

Now, in order to get the id, id_token, and access_token, I need to follow the OAuth process. This is roughly described here. The thing is that in the document they don’t describe clearly how to do the Cloud Functions needed for this purpose

I wrote a couple of functions in Nodejs that allowed me to retrieve this authData

With this function, I can redirect to the Google Sign In page, and retrieve the code

  app.get("/googleSignIn", function (req, res) {
  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(
    CONFIG.oauth2Credentials.client_id,
    CONFIG.oauth2Credentials.client_secret,
    CONFIG.oauth2Credentials.redirect_uris[0]
  );
  // Obtain the google login link to which we'll send our users to give us access
  const loginLink = oauth2Client.generateAuthUrl({
    // Indicates that we need to be able to access data continously without the user constantly giving us consent
    access_type: "offline",
    // Using the access scopes from our config file
    scope: CONFIG.oauth2Credentials.scopes,
  });
  return res.render("index", { loginLink: loginLink });
});

Then, with this code below I get the id, id_token and access_token

app.get("/redirect", function (req, res) {
  // Auth data for Parse
  const authData = {
    google: {
      id: "",
      id_token: "",
      access_token: "",
    },
  };
  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(
    CONFIG.oauth2Credentials.client_id,
    CONFIG.oauth2Credentials.client_secret,
    CONFIG.oauth2Credentials.redirect_uris[0]
  );

  if (req.query.error) {
    // The user did not give us permission.
    return res.status(500).send('Server Error');
  } else {
    oauth2Client.getToken(req.query.code, (err, token) => {
      if (err) return res.status(500).send('Server Error');
      oauth2Client.setCredentials({ access_token: token.access_token });
      var oauth2 = google.oauth2({
        auth: oauth2Client,
        version: "v2",
      });

      oauth2.userinfo.get(function (err, user) {
        if (err) return res.status(500).send('Server Error');
          authData.google.id = user.data.id;
          authData.google.id_token = token.id_token;
          authData.google.access_token = token.access_token;
          return res.status(200).json(authData);
      });
    });
  }
});

The thing is that I need googleapis to make this work. But, I cannot install those API in the Cloud Function.

At this point, I got stuck. It’s been two days since I am trying to get this done. Have anyone managed to get an end-to-end flow to use Google OAuth SignIn with React?

Any help will be appreciated

Guz

I was digging into the subject, and I realized why I don’t see the googleapis at the Cloud Function. I needed to add a package.json file at the deploy folder with the entry

{ 
    "dependencies": {
        "googleapis": "^61.0.0"
    }
}

Now that I see the APIs, I created this Cloud Function to trigger the callback and retrieve the code

Parse.Cloud.define("GoogleSignIn", async (request) => {
      const google = require("googleapis").google;
      const CONFIG = require("./config");
      // Google's OAuth2 client
      const OAuth2 = google.auth.OAuth2;
      // Create an OAuth2 client object from the credentials in our config file
      const oauth2Client = new OAuth2(
        CONFIG.oauth2Credentials.client_id,
        CONFIG.oauth2Credentials.client_secret,
        CONFIG.oauth2Credentials.redirect_uris[0]
      );
    // Obtain the google login link to which we'll send our users to give us access
      const loginLink = oauth2Client.generateAuthUrl({
    // Indicates that we need to be able to access data continously without the user constantly giving us consent
        access_type: "offline",
        // Using the access scopes from our config file
        scope: CONFIG.oauth2Credentials.scopes,
      });
      return loginLink;
});

Then, I created this second function to retrieve the id, id_token and access_token

Parse.Cloud.define("GoogleRedirect", async (request) => {
  const google = require("googleapis").google;
  const CONFIG = require("./config");
  // Google's OAuth2 client
  const OAuth2 = google.auth.OAuth2;
  // Create an OAuth2 client object from the credentials in our config file
  const oauth2Client = new OAuth2(
    CONFIG.oauth2Credentials.client_id,
    CONFIG.oauth2Credentials.client_secret,
    CONFIG.oauth2Credentials.redirect_uris[0]
  );

  if (request.params.query.error) {
    // The user did not give us permission.
    return request.params.query.error;
  } else {
    try {
      const { tokens } = await oauth2Client.getToken(request.params.query.code);
      oauth2Client.setCredentials(tokens);
      var oauth2 = google.oauth2({
        auth: oauth2Client,
        version: "v2",
      });
      const usr_info = await oauth2.userinfo.get();
      // Auth data for Parse
      const authData = {
        google: {
          id: usr_info.data.id,
          id_token: tokens.id_token,
          access_token: tokens.access_token,
        },
      };
     return authData;
    } catch (error) {
      return error;
    }
  }
});

Now, the last part that is missing is to “glue” that with my Front-End application in React

What have you tried so far on your frontend?

Basically, I call

const res = await Parse.Cloud.run("GoogleSignIn");

This will return the link to authenticate the user with Google. Then, I redirect to that link using

window.location.href = oauthLink;

When the user login, Google will redirect to the link that I specified in my Google Settings (e.g. http://localhost:3000/redirect)

Then, on page Redirect I run this code when the page loads

const authData = await Parse.Cloud.run("GoogleToken", params);
const user = new Parse.User();
const res = await user.linkWith("google", { authData });

In my Database Browser, I saw that the user is created and that the id, id_token, and access_token are inside authData

However, I don’t have the correct username and the email is not there neither. So, I need to adapt that

It looks you made a good progress so far. Let me know if you need any help.

Thanks :+1:

There is actually one thing. Apart from the id, id_token and access_token, I returned the user name and email. Then, right before using linkWith, I do this

const authData = await Parse.Cloud.run("GoogleToken", params);
const user = new Parse.User();
user.set("username", authData.name);
user.set("email", authData.email);
const res = await user.linkWith("google", { authData });

This allows me to properly store the user name and email. However, the verification email is not sent when the user SignUp for the first time.

Instead, when I use the Parse.User.signUp() I do receive the verification email

Is this a functionality that is not supported, or some bug within the platform?

I believe the verification e-mail is not sent when you use the Google authentication by default since Google already handled that for you.

Well, not really. Maybe what I can do at the Cloud Function is to Verify by default when the user signs up with a 3rd Pary Application. In that way the functions that can only be accessed by verified users are available.

Now, the other issue that I have is: if a user already signed up with the email and tries to login with the Google Account, it doesn’t work. I received an error, the user already exists. Any tips on how to fix that?

You need to fetch the current user and call linkWith function to this existing instance and not to a new one.