DEV Community

Sahil kashyap
Sahil kashyap

Posted on • Updated on

Google identity service/Google Auth 2022 in React node

Problem:

Beginning April 30th, 2022 new web applications must use the Google Identity Services library, existing web apps may continue using the Platform Library until the March 31, 2023 deprecation date.

  1. Create a helper in react let's name it GoogleOauth with 3 function
    • loginUser (uses popup)
    • loginUser2 (uses one tap)
    • SignUp
# Note

we have 2 login cuz Google One tap UI login has features#exponential_cool_down if user clicks on 'X',the One Tap will be disabled for a while. if user declines one tap login he'll see popup(loginUser)

// googleOauth.js

const id = "xxxxxx";
//generate this id(web oauth) from google console

const createScript = () => {
  // load the sdk
  const script = document.createElement("script");
  script.src = "https://accounts.google.com/gsi/client";
  script.async = true;
  script.onload = initGoogleGSI;
  document.body.appendChild(script);
};

createScript();
const initGoogleGSI = () => {
  console.log("initGoogleGSI SDK initialized");
};

export const loginUser = async () => {
  const client = window.google.accounts.oauth2.initTokenClient({
    client_id: id,
    scope: `profile email`,
    callback: "", // defined at request time
  });
  const tokenResponse = await new Promise((resolve, reject) => {
    try {
      // Settle this promise in the response callback for requestAccessToken()
      client.callback = (resp) => {
        if (resp.error !== undefined) {
          reject(resp);
        }

        // console.log("client resp",resp);
        resolve(resp);
      };
      // console.log("client",client);
      client.requestAccessToken({ prompt: "consent" });
    } catch (err) {
      console.log(err);
    }
  });
  return tokenResponse;
};
export const SignUpUser = async () => {
const SCOPES = ["https://www.googleapis.com/auth/user.birthday.read",
  "https://www.googleapis.com/auth/profile.agerange.read",
  "https://www.googleapis.com/auth/userinfo.profile",
  "https://www.googleapis.com/auth/userinfo.email",
  "https://www.googleapis.com/auth/user.gender.read",
].join(" ");
  const client = window.google.accounts.oauth2.initTokenClient({
    client_id: id,
    scope: SCOPES,
    callback: "", // defined at request time
  });
  const tokenResponse = await new Promise((resolve, reject) => {
    try {
      // Settle this promise in the response callback for requestAccessToken()
      client.callback = (resp) => {
        if (resp.error !== undefined) {
          reject(resp);
        }

        // console.log("client resp",resp);
        resolve(resp);
      };
      // console.log("client",client);
      client.requestAccessToken({ prompt: "consent" });
    } catch (err) {
      console.log(err);
    }
  });
  return tokenResponse;
};

export const loginUser2 = async () => {
  const tokenResponse = await new Promise((resolve, reject) => {
    try {
      const goog = window.google.accounts.id;
      const client = goog.initialize({
        client_id: id,
        scope: `profile email`,
        callback: handleCredentialResponse, // defined at request time
      });
      // Settle this promise in the response callback for requestAccessToken()
      function handleCredentialResponse(resp) {
        if (resp.error !== undefined) {
          reject(resp);
        }

        // console.log("client resp",resp);
        resolve(resp);
      }
      // console.log("client",client);
      window.google.accounts.id.prompt((notification) => {
        if (notification.isNotDisplayed() || notification.isSkippedMoment()) {
          window.google.accounts.id.prompt();
          console.log("Prompt cancelled by user");
          resolve(loginUser());
        }
      });
    } catch (err) {
      console.log("loginUser2 err", err);
    }
  });
  return tokenResponse;
};

Enter fullscreen mode Exit fullscreen mode

In your React Login page

import { GoogleLoginButton } from "react-social-login-buttons";
import * as GoogleInit from "../../../utils/googleOauth";
class LoginPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      userId: "",
      password: "",
    };

    this.loginGoogle = this.loginGoogle.bind(this);

    this.GoogleSignInResponse = this.GoogleSignInResponse.bind(this);
  }

  render() {
    return (
      <div>
        {" "}
        <GoogleLoginButton
          align={"center"}
          onClick={(e) => this.loginGoogle(e)}
        >
          <span>Sign in with Google</span>
        </GoogleLoginButton>
      </div>
    );
  }

  async loginGoogle(e) {
    e.preventDefault();
    // console.log("loginGoogle");
    try {
      //our helper
      let data = await GoogleInit.loginUser2();

      // console.log("signInGoogle.signInGoogle",data);
      this.GoogleSignInResponse(data);
    } catch (error) {
      console.error(error);
    }
  }

  GoogleSignInResponse(value) {
    console.log("GoogleSignInResponse", value);
//send to backend (redux used here)
    this.props.dispatch(
      UserActions.googlelogin(value, (response) => {
        console.log("Response from DB", response);
        if (response.data.dob == "1000-12-01") {
          this.props.history.push("/birthday-wall");
          return;
        }
        if (response.status) {
          this.props.history.push("/");
        } else {
          let error = response.data.message
            ? response.data.message
            : "Something went wrong, try again later!";
          alert(error);
        }
      })
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

In sign up page

import { GoogleLoginButton } from "react-social-login-buttons";
import * as GoogleInit from "../../../utils/googleOauth";
class SignupPage extends Component {
  constructor(props) {
    super(props);

    this.state = {
      userId: "",
      password: "",
    };

    this.signupGoogle = this.signupGoogle.bind(this);

    this.GoogleSignUpResponse = this.GoogleSignUpResponse.bind(this);
  }

  render() {
    return (
      <div>
        {" "}
        <GoogleLoginButton
          align={"center"}
          onClick={(e) => this.loginGoogle(e)}
        >
          <span>Sign in with Google</span>
        </GoogleLoginButton>
      </div>
    );
  }

  async signupGoogle(e) {
    e.preventDefault();
    // console.log("signupGoogle");
    try {
      let data = await GoogleInit.SignUpUser();

      // console.log("signInGoogle.signInGoogle",data);
      this.GoogleSignUpResponse(data);
    } catch (error) {
      console.error(error);
    }
  }
  GoogleSignUpResponse(value) {
    //send to backend
    this.props.dispatch(
      UserActions.Googlesignup(value, (response) => {
        console.log("DB_response", response);
        if (response.status) {
          if (response.data.dob == "1000-12-01") {
            this.props.history.push("/birthday-wall");
            return;
          }
          this.props.history.push("/");
        } else {
          let error;
          if (response.data.error.code == 11000) {
            error = "Data Already Exists";
          } else {
            error = response.data.message
              ? response.data.message
              : "Something went wrong, try again!";
          }

          alert(error);
        }
      })
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Backend nodejs(function i used)


var mongoose = require("mongoose"),
Customer = mongoose.model("Customer"),
SocialLogin = Helpers.socialLogin;
const moment = require("moment");
function UserDataGoogle(response) {
    if(response.payload) {
        return response.payload;
    }
    return response;
}
async function getgoogleDOB(googlePeopleAPiData) {
    let dob;
    let agerange = await googlePeopleAPiData.ageRange;
    const {
        year,
        month,
        day
    } = googlePeopleAPiData.birthdays[0].date;
    dob = `${year}-${month}-${day}`;
    if(!year) {
        if(agerange == 'TWENTY_ONE_OR_OLDER') {
            dob = `1998-${month}-${day}`;
        } else {
            dob = false;
        }
    }
    return dob;
}

function checkForKeysinAPIdata(neededKeys, apidata) {
    return neededKeys.every(key => Object.keys(apidata).includes(key));
}

function createCustomerFromSocialMedia(customer) {
    return stripe.createCustomer(customer).then(result => {
        customer.stripeID = result.id
        return Customer.create(customer).then(async function(user) {
                return user;
            }).then(function(u) {
                return Customer.findOne({
                    _id: u._id
                })
            }) // We need a Customer instance for the then statement of tokenize to work. generateVeirficationEmail restricts the returned user and is not an instane of the Customer model.
            .then(Customer.tokenize);
    });
}
var signUPGoogleCustomer = endPointHandler(async function(req) {
    var customer = req.body;
    const {
        tokenId,
        googleId,
        access_token
    } = customer;
    let GoogleUserInfo = await SocialLogin.axiosGet(`https://www.googleapis.com/oauth2/v3/userinfo?access_token=${access_token}`);
    const {
        email_verified,
        email,
        given_name,
        family_name
    } = GoogleUserInfo;
    let googlePeopleAPiData = await SocialLogin.axiosGet(`https://people.googleapis.com/v1/people/me?personFields=birthdays,genders,age_range&access_token=${access_token}`);
    const neededKeys = ['birthdays', 'ageRange'];
    let googlecustomer = {};
    if(!checkForKeysinAPIdata(neededKeys, googlePeopleAPiData)) {
        let keys = Object.keys(googlePeopleAPiData);
        let difference = neededKeys.filter(x => !keys.includes(x)).toString();
        // throw {
        //   status: 403,
        //   message: `Unable to read ${difference}`
        // };
        //set default date in db if date,agerange not found
        googlecustomer.dob = `1000-12-01`;
    } else {
        let dob = await getgoogleDOB(googlePeopleAPiData);
        if(!dob) {
            throw {
                status: 403,
                message: "User is not over the age of 21 or Invalid Birthdate"
            };
        }
        googlecustomer.dob = dob;
    }
    googlecustomer.email = email;
    googlecustomer.name = {
        first: given_name,
        last: family_name
    };
    googlecustomer.isEmailVerified = email_verified;
    googlecustomer.password = Math.random().toString(36).slice(2, 10);
    return createCustomerFromSocialMedia(googlecustomer);
});
var GoogleCustomerLogin = async function(req, res, next) {
    var customer = req.body;
    const {
        credential, access_token
    } = customer;
    let decode;
    if(credential) {
        decode = Helpers.jwt.decodeJwt(credential);
    } else {
        decode = await SocialLogin.axiosGet(`https://www.googleapis.com/oauth2/v3/userinfo?access_token=${access_token}`);
    }
    const {
        email
    } = UserDataGoogle(decode);
    let result = Customer.findOne({
        email: email
    }).then(function(user) {
        try {
            var Guser = user.restrict();
            return res.status(200).json({
                user: Guser,
                token: jwt.signJwt(Guser)
            });
        } catch(e) {
            return res.status(400).json({
                message: "User not found, Please Signup!",
                error: e,
                status: 400
            });
        }
    })
};
Enter fullscreen mode Exit fullscreen mode

Previously I was verifying the token I was recieving, I guess we don't have to use them

//Social login
const axios = require('axios');
var config = require("../config").dev.google;//google client id env
const {OAuth2Client} = require('google-auth-library');
const GoogleClient= new OAuth2Client(config.client_id);
async function axiosGet(url) {
  try {
    const {data:response} = await axios.get(url) //use data destructuring to get data from the promise object
    return response;
  }
  catch (error) {
    console.log(error);
  }
}
async function googleVerifyId(token) {
  const ticket = await GoogleClient.verifyIdToken({
      idToken: token,
      audience: config.client_id,  // Specify the CLIENT_ID of the app that accesses the backend
  });
  const payload = ticket.getPayload();
  const userid = payload['sub'];
  return payload;
}
async function googleTokenInfo(token) {
  // after acquiring an oAuth2Client...
const tokenInfo = await GoogleClient.getTokenInfo(token);
// console.log("tokeninfo",tokenInfo);
  return tokenInfo;
}
module.exports = {
  axiosGet,
  googleVerifyId,
  googleTokenInfo,
}
Enter fullscreen mode Exit fullscreen mode

Discussion (0)