Google identity service/Google Auth 2022 in React node
Tags: gis, react, login, javascript
Published: 2022-06-14 19:38:27

### 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](https://developers.google.com/identity/one-tap/web/guides/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)

```js
// 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;
};

```

In your React Login page
```js
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);
        }
      })
    );
  }
}

```

In sign up page

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

```

## Backend nodejs(function i used)

```js

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
			});
		}
	})
};
```
Previously I was verifying the token I was recieving, I guess we don't have to use them
```js
//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,
}
```