How to implement Google oauth in React and Node/express
12
Shares
Sun Jan 24 2021

How to implement Google oauth in React and Node/express

Awa Dieudonne

Awa Dieudonne

1.6K views

 In this article, I will walk you through implementing google OAuth with passportjs and JWT in a React, Node, Express, Sequelize, and PostgreSQL application. The same flow can be used on a Next.js application as well.


In the end, we should have a button labeled “Sign In With Google”.


What is Google Oauth?

Oauth stands for Open Authorization. It allows third-party services like Google and Facebook to exchange your information without you having to give away your passwords. This helps prevent your password from being compromised. Learn more about Google Oauth here.


How does Google Oauth work?

Once a user clicks on the Oauth login button, our frontend sends a request to the Google authorization server through a browser redirect and obtains an access token that will grant access to the Google API. During the access-token request, our application sends one or more values in the scope parameter which controls the set of resources and operations that an access token permits.


After obtaining the access token, what happens next?


Our application makes a call to the Google API, setting the token in the HTTP Authorization request header. The access token will be valid only for the set of operations and resources described in the scope of the token request. 

Our request is authenticated using passport.js authentication and sends the user’s profile info. We will receive the user’s profile info inside a passport verify callback whose purpose is to find the user that possesses the set of credentials. 


Once authentication is successful, a session is established and maintained via a cookie set in the user’s browser. Each subsequent request will not contain credentials, but rather the unique cookie that identifies the session. Passport uses the serialize and deserialize functions to save the user’s ID to and from the session.


Google Oauth Flow diagram


Understanding passport Serialize and Deserialize functions

  • serializeUser: Determines which data of the user object should be stored in the session. The result of the serializeUser method is attached to the session as req.session.passport.user 
  • In our case only the user ID will be serialized to the session, keeping the amount of data stored within the session small.
  • deserializeUser: Used to retrieve the id stored in the session by the serializeUser function. This is where we make a backend call to get the user instance by that ID and attach it to the request object as req.user.


Visit http://www.passportjs.org/docs/configure/ for more info about serializeUser and deserializeUser functions.


After deserializing, our next handler receives control, generates a JWT token from the credentials in req.user, and sends a response containing the user’s object and the JWT.


Feel free to comment if there’s anything you do not understand about the above explanations.


Implementing Google Oauth in Nodejs, express, sequelize, and Postgres

1. Create a Google client ID and client secret

Visit Google API Console and obtain OAuth 2.0 credentials such as a client ID and client secret that will be known to both Google and our application.

Below are the steps to follow:

- From the projects drop-down, create a new one by clicking on New project.

- In the sidebar under “APIs & Services”, select Credentials

- In the Credentials tab, click the Create credentials drop-down list, and choose OAuth client ID.

- Configure the consent screen and set the scopes. When you’re done, go back to the dashboard.

- Click again on Create credentials drop-down list, and select OAuth client ID, but this time it will take you to a page where you will select the application type. Choose Web application.

- Under Authorised JavaScript origins, enter the backend API’s URL, which in our case is going to be http://localhost:3000

- Under Authorised redirect URIs, enter the frontend URL which is going to be: http://localhost:4000, and click save.


2. Initialize Nodejs and install all dependencies

Create an empty folder and open it in your favorite editor. Run the following to initialize a Nodejs project.

npm init


After that is complete, run the following to install all necessary dependencies

npm i body-parser cors dotenv express jsonwebtoken passport passport-google-token pg sequelize


Install Dev dependencies

npm i --save-dev @babel/cli @babel/core @babel/node @babel/plugin-proposal-optional-chaining @babel/plugin-transform-runtime @babel/preset-env @babel/register @babel/runtime babel-node nodemon sequelize-cli


3. Configure Express server

// api/server.js
import cors from 'cors';
import logger from 'morgan';
import express from 'express';
import bodyParser from 'body-parser';
const app = express();
app.use(cors({ origin: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(logger('dev'));
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on http://localhost:${port}`));


4. Setup Postgres database connection with Sequelize

Step 1: Create a .sequelizerc file with the following code

const path = require('path');
module.exports = {
  'config': path.resolve('src/db', 'config.js'),
  'models-path': path.resolve('src/db', 'models'),
  'seeders-path': path.resolve('src/db', 'seeders'),
  'migrations-path': path.resolve('src/db', 'migrations')
}


Step 2: Initialize sequelize 

sequelize init


This will create the following folders

  • config.js, which tells the Sequelize CLI how to connect to the database
  • models, contains all models for your project
  • migrations, contains all migration files
  • seeders, contains all seed files


Step 3: Configure Postgres database connection

Let’s start by creating our .env file to store our environment variables such as our google client key, JWT secret, Database connection URL, …etc

Copy the Google Client Id and Secret Id from your Google API Console dashboard and save them inside the .env file.


// .env
DATABASE_DEV=postgres://postgres:{{PASSWORD}}@{{HOST}}:{{PORT}}/oauth_DB
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
JWT_SECRET=


Copy and paste the following connection setup code inside config.js file.

// api/db/config.js
require('dotenv').config();
module.exports = {
  development: {
    database: 'oauth_DB',
    use_env_variable: 'DATABASE_DEV',
    dialect: 'postgres',
  },
  production: {
    database: 'oauth_DB',
    use_env_variable: 'DATABASE_URL',
    dialect: 'postgres',
    dialectOptions: {
      ssl: {
        require: true,
        rejectUnauthorized: false
      }
    }
  }
};


This file is supposed to set up a connection to our database, but we do not have one yet. So run the following sequelize command to create the database for our API

sequelize db:create


Step 4: Create a migration file for the users' table

Now that we are done with the configurations, let’s create the users’ migration file with all its columns by running the following command:

sequelize model:generate --name User --attributes name:string,email:string,password:string,goodleId:string,provider:string


As soon as this is done running, let us create the database table by executing the migration as follows:

sequelize db:migrate


5. Create Google Oauth endpoint


Step 1: Configure passport.js and google strategy:

Create a controller folder in the /api directory and create another new folder for authController with index.js and passport.js in it.


Copy and paste the following code into the passport.js file you just created.

import passport from 'passport';
import { config } from 'dotenv';
import { User } from '../../db/models';
const GoogleTokenStrategy = require('passport-google-token').Strategy;
config();
const getProfile = (profile) => {
  const { id, displayName, emails, provider } = profile;
  if (emails && emails.length) {
    const email = emails[0].value;
    return { googleId: id, name: displayName, email, provider };
  }
  return null;
};
passport.use(
  new GoogleTokenStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET
    },
    //  Passport verify callback
    async (accessToken, refreshToken, profile, done) => {
      try {
        const existingGoogleUser = await User.findOne({
          where: { googleId: profile.id }
        });
        if (!existingGoogleUser) {
          const existingEmailUser = await User.findOne({
            where: { email: getProfile(profile).email }
          });
          // Create user if he is not registered already
          if (!existingEmailUser) {
            const newUser = await User.create(getProfile(profile));
            return done(null, newUser);
          }
          return done(null, existingEmailUser);
        }
        return done(null, existingGoogleUser);
      } catch (e) {
        throw new Error(e);
      }
    }
  )
);
// Saves user's ID to a session
passport.serializeUser((user, done) => {
  done(null, user.id);
});
// Retrieve user's ID from a session
passport.deserializeUser((id, done) => {
  User.findByPk(id).then((user) => {
    done(null, user);
  });
});


So we imported our google strategy and created an instance of it inside the use() function. Inside the verify callback we are getting the user’s profile coming from google to check if that user already signed up through Google Oauth, else we sign him up and call done(null, user).


Step 2: Create a controller function for completing the Oauth flow:

Inside our controller/index.js file, copy and paste the following code

import jwt from 'jsonwebtoken';
import models from '../../db/models';
const { User } = models;
const AuthController = {
  async googleOauth(req, res) {
    if (!req.user) {
      return res.status(400).send('Authentication failed!');
    }
    const { email } = req.user;
    const user = await User.findOne({ where: { email } });
    const token = jwt.sign({ id, email }, process.env.JWT_SECRET);
    return res.status(200).send({ token, user });
  }
};
export default AuthController;


What this googleOauth does is to receive the authenticated user from the req object generate a JWT token and return a response of the user and his login token.


Step 3: Connect passport.js configurations to the server and add a route:

Import the configurations from controllers/passport.js inside our express server and create the endpoint for google oauth. The server should now look like this:


// api/server.js
import cors from 'cors';
import logger from 'morgan';
import express from 'express';
import bodyParser from 'body-parser';
import './controllers/Auth/googlePassport';
const app = express();
app.use(cors({ origin: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(logger('dev'));
// Route
app.get('/', (req, res) => {
  res.status(200).send({
    message: 'Welcome to the API!'
  });
});
/**
 * GOOGLE OAUTH ROUTE: /auth/google
 */
app.post('/auth/google', passport.authenticate(
  'google-token', { session: false }), Auth.googleOauth
);
// Error handler middleware
app.use((err, req, res, next) => {
  if (err) {
    if (err.statusCode) {
      return res.status(err.statusCode).send({
        error: err.message
      });
  }
  return res.status(400).send({
    error: message
  });
  }
});
const port = process.env.PORT || 5000;
app.listen(port, () =>
  console.log(`Server running on http://localhost:${port}`));


And then we are done with Implementing Google Oauth in Nodejs, express, sequelize, and Postgres. Let’s go ahead and consume the oauth endpoint on the frontend.


Implementing Google Oauth in ReactJs

1. Initialize create-react-app:

Initialize a new react project by running the following in your term

npx create-react-app google-oauth-client


2. Install all necessary dependencies:

npm i --save axios react-google-login js-cookie


3. Use GoogleLogin button component from react-google-login:

Let’s first of all setup our environment variables. 

Create a .env file in the root directory of the application and add the following variables to it.


// .env
REACT_APP_API_BASE_URL=http://localhost:3000
REACT_APP_GOOGLE_CLIENT_ID=  // Enter the client key you used on the backend API


Inside our App.jsx component, copy and paste the following code

import axios from 'axios';
import Cookie from 'js-cookie';
import { GoogleLogin } from 'react-google-login';
export const axiosApiCall = (url, method, body = {}) => axios({
    method,
    url: `${process.env.REACT_APP_API_BASE_URL}${url}`,
    data: body,
  });
export default function App() {
  const onGoogleSuccess = (response) => {
    const access_token = response.accessToken;
    axiosApiCall(
      '/auth/google',
      'post',
      { access_token }
    ).then((res) => {
      const { user, token } = res.data;
      // Save the JWT inside a cookie
      Cookie.set('token', token);
    }).catch((err) => {
      throw new Error(err);
    })
  }
  const onGoogleFailure = () => {}
  return (
    <div style={{
      width: '100%',
      height: '100vh',
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      alignItems: 'center',
      backgroundColor: '#151a30',
      color: 'white'
    }}>
      <h1>Google Oauth Sign In</h1>
      <GoogleLogin 
        clientId={process.env.REACT_APP_GOOGLE_CLIENT_ID}
        buttonText="Sign in with Google"
        onSuccess={onGoogleSuccess}
        onFailure={onGoogleFailure}
        className="google-login-button" />
    </div>
  );
}


You should have something like this as the end result



Test it out, if you face any issues, let me know in the comment section, I am available 24/7 to answer your questions. 


Conclusion

So that is it for setting up google oauth. Now I’d like to hear what you have to say. 

Have you learned something new? Drop it in the comment section, we never can tell how many people will learn from us. Was it helpful? Spread the word, share with friends even on social media.


Thanks

ReactJSNodeJSPostgreSQLSequelizeJavaScript
0 comment(s)

Leave a Comment

Your email address will not be published. Required fields are marked *

Follow

Interested in hearing more from me?
© 2021. All Rights Reserve.