DEV Community

Cover image for Easy Google OAuth2 Authentication in Nodejs
atultyagi612
atultyagi612

Posted on • Updated on

Easy Google OAuth2 Authentication in Nodejs

Alt Text
IMAGE CREDITS
In this project, we simply use the passport google Strategy.
Passport is authentication middleware for Node.js. Extremely flexible and modular, a Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more. reference
Before we get start assume that you have a good knowledge of JavaScript and Nodejs.
so without any further delay let's start πŸ‘

Google credentials

First, we have to get Google's credentials.
To get credentials 'if don’t already have them ' go to Google developer Console

1)create a new project
2)Select the project and click credentials and then select OAuth client ID
3)Now Select Web Application in application type.
4)Input your app name or whatever else you like, in Authorized JavaScript origins add this linehttp://localhost:3000 and in Authorized redirect URIs field add this line http://localhost:5000/auth/google/callback and click to create.
5)Now copy your Google client ID and Google client secret

Help

Lets Initialize the New Project

To initialize the new project you just need to create a new folder "App name" and open a folder in visual studio (or any other IDE ) code or any other IDE and run the below code in the command line

 npm init  
Enter fullscreen mode Exit fullscreen mode

Just fill in the project name and any other detail or just skip. After the package.json file is generated.

Structure of the project

Alt Text
As with the reference of the above image create folders and files leave node_modules package-lock and package-json as they generate automatically.

Install Dependencies

These are the Dependencies we need to install for our project.

express
ejs
connect-mongo
dotenv
express-session
mongoose
passport
passport-google-oauth20
Enter fullscreen mode Exit fullscreen mode

Install Dependencies by writing the below code in your terminal

npm i ejs connect-mongo dotenv express-session mongoose passport passport-google-oauth20
Enter fullscreen mode Exit fullscreen mode

Setup App for a run

To start the server automatically we just need to install Nodemon which restart the server automatically when any change is detected

npm i -D nodemon
Enter fullscreen mode Exit fullscreen mode

Setup application for developer run and normal run. Just change the Script section with the below code in package.json.

"scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
Enter fullscreen mode Exit fullscreen mode

Start a local server

To start our app for testing/developer just simply type the following command in the command line:

npm run dev
Enter fullscreen mode Exit fullscreen mode

The main work starts from there

You need to just put your google client id and secret in this file. And also the MongoDB URI like(mongodb://localhost:27017/) if you are hosting MongoDB from your system. if you are using Mongodb Atlas it like(mongodb+srv://XXXX:XXXX@cluster0.obaan.mongodb.net/{DBNAME}?retryWrites=true&w=majority)
file:config/config.env

PORT = 3000
MONGO_URI=mongodb+srv://XXXX:XXXX@cluster0.obaan.mongodb.net/{DBNAME}?retryWrites=true&w=majority
GOOGLE_CLIENT_ID = XXXXXXXXXX
GOOGLE_CLIENT_SECRET = XXXXXXXXXXXXXXXX
Enter fullscreen mode Exit fullscreen mode

In my case we use Mongodb Atlas . you can refer this for getting mongodb atlas URI . and refer this for Google client id and secret if any problem occur .

Application

Its time code our app.js file this is the main file and it will sit in the root of our website.
In this file we have to setup our server.

file:app.js

Import all the necessary modules.

const express = require('express');
const mongoose=require('mongoose');
const dotenv = require('dotenv')
const passport = require('passport')
const session = require('express-session')
const MongoStore = require('connect-mongo')(session)
require('./config/passport')(passport)
Enter fullscreen mode Exit fullscreen mode

Connect to mongodb and set express template.

var app=express();
const PORT = process.env.PORT||3000;
dotenv.config({ path: './config/config.env' })

mongoose.connect(process.env.MONGO_URI,{
    useNewUrlParser:true,
    useUnifiedTopology: true
})
app.use(express.static('public'))
app.set('view engine','ejs');
Enter fullscreen mode Exit fullscreen mode

Initialize middleware and setup database for storing sessions.

app.use(express.urlencoded({extended:true}))
app.use(
    session({
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: false,
      store: new MongoStore({ mongooseConnection: mongoose.connection }),
    })
  )
  // Passport middleware
app.use(passport.initialize())
app.use(passport.session())
Enter fullscreen mode Exit fullscreen mode

last part import routes

app.use(require("./routes/index"))
app.use('/auth', require('./routes/auth'))

app.listen(PORT,console.log(`listening at ${PORT}`))
Enter fullscreen mode Exit fullscreen mode

Now our app.js file is readyπŸŽ‰πŸŽ‰

Routes

Now its time to code our routes
we are to code 2 routes files one auth.js for authentication and another one index.js for redirecting between pages
Let's code out the auth.js file.
file:auth.js

//Importing required modules 
const express = require('express')
const passport = require('passport')
const router = express.Router()
Enter fullscreen mode Exit fullscreen mode

send to google to do the authentication.
In scopes, profile gets us their basic information including their name and email gets their emails.


router.get('/google', passport.authenticate('google', { scope: ['profile','email'] }))

Enter fullscreen mode Exit fullscreen mode

Callback after google has authenticated the user.

router.get(
  '/google/callback',
  passport.authenticate('google', { failureRedirect: '/' }),
  (req, res) => {
    res.redirect('/log')
  }
)
Enter fullscreen mode Exit fullscreen mode

For logout

router.get('/logout', (req, res) => {
  req.logout()
  res.redirect('/')
})

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Now our auth.js file is readyπŸŽ‰πŸŽ‰

Before creating the index.js file we have to create our middleware to ensure that the user is authenticated or not.

file:middleware/auth.js

module.exports = {
  // if user is authenticated the redirected to next page else redirect to login page
  ensureAuth: function (req, res, next) {
    if (req.isAuthenticated()) {
      return next()
    } else {
      res.redirect('/')
    }
  },
  // if user is authenticated and going to login page then redirected to home page if not authenticated redirected to login page  .
  ensureGuest: function (req, res, next) {
    if (!req.isAuthenticated()) {
      return next();
    } else {
      res.redirect('/log');
    }
  },
}

Enter fullscreen mode Exit fullscreen mode

Now our middleware is ready let's code our next router index.js.
file:routes/index.js


const router = require('express').Router()
//importing middleware
const { ensureAuth, ensureGuest } = require('../middleware/auth')

router.get('/', ensureGuest ,(req, res) => {
    res.render('login')
  })

router.get("/log",ensureAuth, async(req,res)=>{
    res.render('index',{userinfo:req.user})
})
module.exports=router;
Enter fullscreen mode Exit fullscreen mode

Configure Passport's Google startegy

file:config/passport.js

// import all the things we need  
const GoogleStrategy = require('passport-google-oauth20').Strategy
const mongoose = require('mongoose')
const User = require('../models/User')

module.exports = function (passport) {
  passport.use(
    new GoogleStrategy(
      {
        clientID: process.env.GOOGLE_CLIENT_ID,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        callbackURL: '/auth/google/callback',
      },
      async (accessToken, refreshToken, profile, done) => {
        //get the user data from google 
        const newUser = {
          googleId: profile.id,
          displayName: profile.displayName,
          firstName: profile.name.givenName,
          lastName: profile.name.familyName,
          image: profile.photos[0].value,
          email: profile.emails[0].value
        }

        try {
          //find the user in our database 
          let user = await User.findOne({ googleId: profile.id })

          if (user) {
            //If user present in our database.
            done(null, user)
          } else {
            // if user is not preset in our database save user data to database.
            user = await User.create(newUser)
            done(null, user)
          }
        } catch (err) {
          console.error(err)
        }
      }
    )
  )

  // used to serialize the user for the session
  passport.serializeUser((user, done) => {
    done(null, user.id)
  })

  // used to deserialize the user
  passport.deserializeUser((id, done) => {
    User.findById(id, (err, user) => done(err, user))
  })
} 
Enter fullscreen mode Exit fullscreen mode

User model

Now it's time to create our database model to user data in the database.
file:models/User.js

const mongoose = require('mongoose')

const UserSchema = new mongoose.Schema({
  googleId: {
    type: String,
    required: true,
  },
  displayName: {
    type: String,
    required: true,
  },
  firstName: {
    type: String,
    required: true,
  },
  lastName: {
    type: String,
    required: true,
  },
  image: {
    type: String,
  },
  email:{
type:String,
required: true,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
})

module.exports = mongoose.model('User', UserSchema)

Enter fullscreen mode Exit fullscreen mode

Good news at that time all the routes, models, and middlewares are ready the only things is ready is our HTML(EJS) Pages.

Login and main pages

Now its time to create our login page using bootstrap.
file:views/login.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.0-2/css/all.min.css"
        integrity="sha256-46r060N2LrChLLb5zowXQ72/iKKNiw/lAmygmHExk/o=" crossorigin="anonymous" />
    <link rel="stylesheet" href="/css/style.css">
    <title>Login</title>
</head>

<body>
    <div class="container login-container">
        <div class="card" style="margin-top:100px;">
            <div class="card-content">
                <div class="section" style="text-align: center;">
                    <a href="/auth/google" class="btn red darken-1">
                        <i class="fab fa-google left"></i> Log In With Google
                    </a>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Lets Create Main page which appear after user login.
file:views/index.ejs

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Done</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
</head>

<body>

    <!-- As a link -->
    <nav class="navbar navbar-light bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="/"><img class="logo" src=<%=userinfo.image %> alt=""> &nbsp; <%=
                    userinfo.firstName %></a>
            <a class="navbar-brand btn btn-danger btn-small" style="color: white;" href="/auth/logout">Logout</a>
        </div>
    </nav>




    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
        crossorigin="anonymous"></script>
    <script src="ejs.min.js"></script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode
Preview

Alt Text

πŸŽ‰πŸŽ‰

Our Google Authentication App is ready.

Now it's your time to use that amazing Middleware passport.js Good Luck 😎🎢

Live Preview

Here is the demo. I use the above code in my project Todo app
Live Preview.
Want to build a to-do app? refer to my this article.

GitHub repo.

Top comments (3)

Collapse
 
rishabh570 profile image
Rishabh Rawat

Hey @atultyagi612

I ran the project on local with no modifications, looks like there is some error being thrown there:

InternalOAuthError: Failed to fetch user profile

I had faced this exact issue on my project as well. I came across your repo looking for a working example but unfortunately your repo is also giving the same error.

I tried your demo heroku app though, it is working fine. So I was wondering if that is running the same code or you had to do some fixes to get around the above error?

Thanks and awesome article πŸ™ŒπŸ»

Collapse
 
sdahirsalman profile image
SDahirSalman

Hello,
Pardon my ignorance, I followed the tutorial step by step and used mongodb atlas as my db engine,now how do I run and access the app, I tried localhost:3000 in my web browser but it did not work. Thank you

Collapse
 
birdy profile image
Chaeah park • Edited

Hi, thanks for using the diagram that I created. Please add a credit if you want to use it in your article. medium.com/@cheahpark/the-big-pict...