DEV Community

Cover image for How to implement Google Authentication in your React Applications!!
Shaan Alam
Shaan Alam

Posted on

How to implement Google Authentication in your React Applications!!

Intro

Hey ya folks!
Have you ever wondered how to implement Google Authentication in your React applications? Well, worry not, because today I am gonna show you exactly how is it done.

But, why is it needed?

OAuth is a Open Standard Authorization protocol that provides appliactions the scopes of the user's data without sharing their password to other applications. It also makes authentication process a lot easier both for the developer and the user. For example, you might have seen "Login with Google" button on some websites. When you click that button, a request is made to Google's servers and the user's data (without password) is returned to the client side. This response can also be used against our own API to authenticate the user.

google login

What are we gonna make?

We are gonna be creating a React App which will use Google OAuth to authenticate the user. For the simplicity of the application I'm gonna store the user's data inside the component state.

What you will learn?

  • Implementing Google Authentication in your React App (pretty obvious πŸ˜…)
  • Creating a Node REST API πŸ’»
  • Using TypeScript on client and server side 😎

Folder Structure

Client Side

πŸ“¦client
┣ πŸ“‚ public
┣ πŸ“‚ src
┃ ┣ πŸ“‚ components
┃ ┃ ┣ πŸ“œ GoogleAuth.tsx
┃ ┣ πŸ“‚ pages
┃ ┃ ┃ β”— πŸ“œ Login.tsx
┃ ┣ πŸ“œ App.tsx
┃ ┣ πŸ“œ index.tsx
┃ ┣ πŸ“œ .env

Server side

πŸ“¦server
┣ πŸ“‚ src
┃ ┣ πŸ“‚ controllers
┃ ┃ β”— πŸ“œ auth.controller.ts
┃ ┣ πŸ“‚ models
┃ ┃ β”— πŸ“œ user.model.ts
┃ ┣ πŸ“‚ routes
┃ ┃ β”— πŸ“œ auth.route.ts
┃ β”— πŸ“œ index.ts
┣ πŸ“œ .env

Let's Go!! πŸƒ

Create a Google Cloud Project

Go to Google Developer Console. Create a new Project. You'll need to configure your OAuthc consent screen. Give your application a name, user supported email, app logo etc. Goto Credentials tab and create credentials.
Select OAuth Client ID and choose the application type as web.
Give your application a name and mention authorized JavaScript origins and redirect origins.
You will get your Client ID. Save this client ID as .env file for both client and server.

google console

Initial Project Setup

First of all, we need to setup our backend and create a REST API to authenticate our user. Create a folder called server and inside it initialize an empty project.

yarn init -y

OR

npm init -y
Enter fullscreen mode Exit fullscreen mode

Install the following dependencies.

yarn add cors dotenv express google-auth-library mongoose
Enter fullscreen mode Exit fullscreen mode

Since I already mentioned that we are going to use TypeScript for our application, we will need to install type definitions for these dependencies. We will install the type definitions as dev dependencies because they are not needed in production.

yarn add @types/cors @types/express @types/mongoose -D
Enter fullscreen mode Exit fullscreen mode

We will also be needing nodemon, ts-node and typescript, let's install them as well

yarn add nodemon ts-node typescript -D
Enter fullscreen mode Exit fullscreen mode

Next, we need to generate a tsconfig.json file. This file contains all the configuration for our TypeScript project like rootDir, compiler options etc.

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

We need to make some changes in the tsconfig.json file.

tsconfig.jsona
tsconfig.json

Also, add the following scripts to your package.json

"scripts": {
    "dev": "nodemon ./src/index.ts",
    "build": "rm -rf && tsc"
  },
Enter fullscreen mode Exit fullscreen mode

Creating an express server

Before creating an express server, I'd like to show you an overall flowchart of how we will be creating our express server.

flowchart

Create a file src/index.ts and inside it we will create a basic express server.

import express from "express";
import authRoutes from "./routes/auth.route";
import mongoose from "mongoose";
import dotenv from "dotenv";
import cors from "cors";

dotenv.config();

const app = express();
const PORT = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());
app.use("/auth", authRoutes);

mongoose.connect(`${process.env.MONGO_URI}`);

const db = mongoose.connection;
db.once("open", () => console.log("Connected to Mongo DB!!"));
db.on("error", (error) => console.error(error));

app.listen(PORT, () =>
  console.log(`The server is up and running on PORT ${PORT} πŸš€`)
);
Enter fullscreen mode Exit fullscreen mode

Let me explain you what is happening here,

import express from "express";
import authRoutes from "./routes/auth.route";
import mongoose from "mongoose";
import dotenv from "dotenv";
import cors from "cors";

dotenv.config();
Enter fullscreen mode Exit fullscreen mode

First we import all these dependencies and the configure dotenv to load our environment variables.

app.use(cors());
app.use(express.json());
app.use("/auth", authRoutes);
Enter fullscreen mode Exit fullscreen mode

Then we define some middlewares here. We make a middleware to use cors(). The second middleware will help us recieve JSON data through requests. And the third middleware is a route middleware.

const db = mongoose.connection;
db.once("open", () => console.log("Connected to Mongo DB!!"));
db.on("error", (error) => console.error(error));

app.listen(PORT, () =>
  console.log(`The server is up and running on PORT ${PORT} πŸš€`)
);
Enter fullscreen mode Exit fullscreen mode

Then we finally connect to our MongoDB database and listen to our express server on PORT 5000.

The User Model

Next, we need to create a user model to save the user documents into the database. Create a models/user.model.ts file.

import mongoose from "mongoose";

interface UserDocument extends mongoose.Document {
  email: string;
  avatar: string;
  name: string;
}

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
  },
  avatar: {
    type: String,
    default: "",
  },
  name: {
    type: String,
    required: true,
  },
});

export default mongoose.model<UserDocument>("User", UserSchema);
Enter fullscreen mode Exit fullscreen mode

Notice, we are only implementing google auth here, so I haven't specified the password field here, however if you are creating an authentication system by yourself, you might probably want to have a password field too.

Controller

We have to create a controller for authenticating our user and sending back the response to the client.

Create a file controllers/auth.controller.ts.

import { Request, Response } from "express";
import { OAuth2Client } from "google-auth-library";
import User from "../models/user.model";

const googleClient = new OAuth2Client({
  clientId: `${process.env.GOOGLE_CLIENT_ID}`,
});

export const authenticateUser = async (req: Request, res: Response) => {
  const { token } = req.body;

  const ticket = await googleClient.verifyIdToken({
    idToken: token,
    audient: `${process.env.GOOGLE_CLIENT_ID}`,
  });

  const payload = ticket.getPayload();

  let user = await User.findOne({ email: payload?.email });
  if (!user) {
    user = await new User({
      email: payload?.email,
      avatar: payload?.picture,
      name: payload?.name,
    });

    await user.save();
  }

  res.json({ user, token });
};
Enter fullscreen mode Exit fullscreen mode

Let me explain what's happening here.

import { Request, Response } from "express";
import { OAuth2Client } from "google-auth-library";
import User from "../models/user.model";

const googleClient = new OAuth2Client({
  clientId: `${process.env.GOOGLE_CLIENT_ID}`,
});
Enter fullscreen mode Exit fullscreen mode

First we import all the necessary dependencies and libraries we want, and then we initialize our google client using the client ID we received from google.

Next, we create and export a authenticateUser function, which is basically our controller.

Inside the authenticateUser function, we grab the token from req.body. (We will send the token from the client)

const { token } = req.body;
Enter fullscreen mode Exit fullscreen mode
const ticket = await googleClient.verifyIdToken({
  idToken: token,
  audient: `${process.env.GOOGLE_CLIENT_ID}`,
});

const payload = ticket.getPayload();
Enter fullscreen mode Exit fullscreen mode

And then we verify the token and get a payload which will contain the details of our user.

let user = await User.findOne({ email: payload?.email });
if (!user) {
  user = await new User({
    email: payload?.email,
    avatar: payload?.picture,
    name: payload?.name,
  });

  await user.save();
}

res.json({ user, token });
Enter fullscreen mode Exit fullscreen mode

Next, we check if the user received from google already exists in our database. If it exists then we return the same user along with the token, or else, we create and save a new user in our database.

Routes

Now we need to run this controller whenever the server hits the /auth endpoint. For that we need to specify routes to our express server. Create a routes/auth.route.ts file. Import the controller and specify it for the / POST route.

import express from "express";
import { authenticateUser } from "../controllers/auth.controller";

const router = express.Router();

router.post("/", authenticateUser); // (This is actually /auth POST route)

export default router;
Enter fullscreen mode Exit fullscreen mode

Client side

Now, that we have our backend ready, it's time for us to work on front end. Initialize a React App.

yarn create react-app --typescript google-login-project
cd google-login-project
Enter fullscreen mode Exit fullscreen mode

Install the following dependencies

yarn add react-google-login react-router-dom axios
yarn add @types/react-router-dom -D
Enter fullscreen mode Exit fullscreen mode

Creating UI

Let's create UI for our application. Inside App.tsx make the following changes

import { Switch, Route } from "react-router-dom";
import GoogleAuth from "./components/GoogleAuth";

const App = () => {
  return <GoogleAuth />;
};

export default App;
Enter fullscreen mode Exit fullscreen mode

GoogleAuth Component

In App.tsx you have seen that we've used a GoogleAuth component. Let's make that one in the components directory.

// /components/GoogleAuth.tsx
import { useState } from "react";
import axios, { AxiosResponse } from "axios";
import GoogleLogin from "react-google-login";

interface AuthResponse {
  token: string;
  user: User;
}

interface User {
  _id: string;
  name: string;
  email: string;
  avatar: string;
}

const GoogleAuth = () => {
  const [user, setUser] = useState<User | null>(null);
  const onSuccess = async (res: any) => {
    try {
      const result: AxiosResponse<AuthResponse> = await axios.post("/auth/", {
        token: res?.tokenId,
      });

      setUser(result.data.user);
    } catch (err) {
      console.log(err);
    }
  };

  return (
    <div className="h-screen w-screen flex items-center justify-center flex-col">
      {!user && (
        <GoogleLogin
          clientId={`${process.env.REACT_APP_CLIENT_ID}`}
          onSuccess={onSuccess}
        />
      )}

      {user && (
        <>
          <img src={user.avatar} className="rounded-full" />
          <h1 className="text-xl font-semibold text-center my-5">
            {user.name}
          </h1>
        </>
      )}
    </div>
  );
};

export default GoogleAuth;
Enter fullscreen mode Exit fullscreen mode

Since this is a simple application I've used conditional rendering here rather than routing. If the user is not set in state, we will render the Google login component else we will display the user's details (avatar and name).

However, if you want, you can store the user's data in redux store or Context API which seems more practical.

Wrapping up ✨

That was it. We've sucessfully implemented Google OAuth in our React Application.
Github repo - https://github.com/shaan-alam/google-login

Find me here 🌍

Github - shaan-alam
Twitter - shaancodes
LinkedIn - Shaan Alam
Instagram - shaancodes

Top comments (4)

Collapse
 
trutheonion profile image
truTheOnion

You forgot to pass clientSecret in OAuth2Client instance, correct version: -

const googleClient = new OAuth2Client({
clientId: ${process.env.GOOGLE_CLIENT_ID},
clientSecret: ${process.env.GOOGLE_CLIENT_SECRET},
});

Collapse
 
marianofarace profile image
Mariano Farace • Edited

Hi Shaan! Let me ask you a general question, I'm confused. The attached image is the flow found at googles documentation. You flow is different. Can you tell me why you did it in a different way, and what are the differences/implications? I've been looking at lot lot of googlaAuth implementation with React and Node, and they are all different, tough could not find one like the one attached in the picture. Thank you!!
dev-to-uploads.s3.amazonaws.com/up...

Collapse
 
shaancodes profile image
Shaan Alam

Thanks for correction πŸ™‚

Collapse
 
thanhtutzaw profile image
ThanHtutZaw

Is it secure when Implementing google authentication without backend (node) ?