How to Implement “Sign in with Google” in Your MERN Stack App with JWT

How to Implement “Sign in with Google” in Your MERN Stack App with JWT

·

4 min read

In this article, we will be integrating Google sign-in for registration or login purpose in our React application using the JWT authentication mechanism in the Node.js server.

Today, users prefer registering and logging into a site with their social accounts rather than repeatedly filling the same tedious forms.

google.jpg

Enough talk, let us start the development.

Frontend of our application

First, install some dependencies that we are going to use.

npm install axios
npm install jwt-decode
npm install react-google-login
npm install react-router-dom

In the app.js file of the src directory, the App function returns a GoogleLogin component. For now, the callback functions of the GoogleLogin component are empty, and the client Id is also not legit.

import { GoogleLogin } from 'react-google-login';

export default function App() {
  return (
    <div className="App">
     <GoogleLogin
    clientId="658977310896-knrl3gka66fldh83dao2rhgbblmd4un9.apps.googleusercontent.com"
    buttonText="Login with google"
    onSuccess={()=>{}}
    onFailure={(}=>{}}
    cookiePolicy={'single_host_origin'}
  />
    </div>
  );
}

The first thing you should do now is creating a client id for our application and replace the fake client id with a real one.

Note:- It is not a good practice to pass the API key to the component. Instead, it should be declared in .env files and then used.

Here are the docs that demonstrate the step by step process to get a client id for your application

Now let us define the callback functions to handle the response of user authentication. Once the user authentication is successful, responseGoogleSuccess will be triggered, and it will make the API calls to store the user's data in the database. If there is an authentication error in the frontend, responseGoogleError will handle that error.

Also, you can console log the response to check what the actual response object is? However, it contains a tokenId that we will send to the server via API call to authenticate the user in the Backend.

import { GoogleLogin } from 'react-google-login';

  const responseGoogleSuccess = async (response) => {
      try {
        const result = await  axios({
          method: 'POST',
          url: `${process.env.server_url}/googlelogin`,
          data: { idToken: response.tokenId }
        });
        console.log(result);

      } catch (error) {
        console.log(error);
      }
  }
  const responseGoogleError = (response) => {
          console.log(response)
  }

export default function App() {
  return (
    <div className="App">
     <GoogleLogin
    clientId="658977310896-knrl3gka66fldh83dao2rhgbblmd4un9.apps.googleusercontent.com"
    buttonText="Login with google"
    onSuccess={responseGoogleSuccess}
    onFailure={responseGoogleError}
    cookiePolicy={'single_host_origin'}
  />
    </div>
  );
}

In the responseGoogleSuccess, the API call is made via Axios. If everything went well in the Backend and database, a success message or whatever the server sends response would be stored in the result that you can check in the console and use it however you want, and if there this error in the Backend, the catch block will handle that.

Backend of our application

Let us start by installing some dependencies.

npm I jsonwebtoken
npm i google-auth-library

Now create a route for handling the API request for google authentication in our routes.js file.

import express from "express";
import {googleLogin} from '../controllers/auth.js';

const authRoutes = express.Router();

authRoutes.post('/googlelogin',googleLogin)

export default authRoutes;

Before making the googleLogin function in the controller, create an environment variable client id that stores the google oAuth API key and a Jwt secret key.

Here the googleLogin function in auth.js in the controller directory.

import User from "../models/user.js";
import jwt from "jsonwebtoken";
import { OAuth2Client } from "google-auth-library";

// handles google login
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
export const googleLogin = (req, res) => {
  const { idToken } = req.body;

  client
    .verifyIdToken({ idToken, audience: process.env.GOOGLE_CLIENT_ID })
    .then((response) => {
      const { email_verified, name, email } = response.payload;

      if (email_verified) {
        User.findOne({ email }).exec((err, user) => {
          if (user) {
            const token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
              expiresIn: "7d"
            });
            const { _id, email, name } = user;
            return res.json({
              token,
              user: { _id, email, name }
            });
          } else {
            const password = email + process.env.JWT_SECRET;

            user = new User({ name, email, password });
            user
              .save((err, data) => {
                if (err) {
                  return res.status(400).json({
                    error: "User signup failed with google"
                  });
                }
                const token = jwt.sign(
                  { _id: data._id },
                  process.env.JWT_SECRET,
                  { expiresIn: "7d" }
                );
                const { _id, email, name } = data;

                return res.json({
                  token,
                  user: { _id, email, name }
                });
              })
              .catch((err) => {
                return res.status(401).json({
                  message: "signup error"
                });
              });
          }
        });
      } else {
        return res.status(400).json({
          error: "Google login failed. Try again"
        });
      }
    });
};

In the above function, we created a client that verifies the token sent by the frontend and returns a response object with email_verified property, which is boolean, and the user's name and email.

If email_verified is true, we will check if the user already exists in the database, and if the user exists, we will send them a token and their data so they can log in. If the user does not already exist, we will first create that user and then do the same.

I hope this will be useful to implement a google authentication system with JWT in your MERN stack app.

Frontend code sandbox: codesandbox.io/s/strange-newton-h74f7?file=..

Backend code sandbox: codesandbox.io/s/goofy-snowflake-lvqf0?file..

Thank you.