Monday, July 13 2020

Build Authentication App with OAuth2.0 and Node.js

In this tutorial explains about How to Build Authentication App with OAuth2.0 and Node.Js OAuth2 is a method for authentication for accessing API. OAuth2.0 allows authorization without the external application getting the user’s email address or password. Instead, the external application gets a token that authorizes access to the user’s account.

How to Build Authentication App with OAuth2.0 and Node.Js

Why OAuth is Better

OAuth is better than other authentication methods. The main difference is OAuth is much secure and credentials can’t be hacked. OAuth is preferred one.

Click here to read about Authentication app using JWT in Node.js

What is OAuth REST API

OAuth authentication that allows a user (owner) to grant permission for external application to access their information (resource).

How Authentication Workflow Works in using OAuth2

  1. When a user trying to login from external application for authorization purpose.
  2. Authorization server allows permission to their allowed data except password.
  3. Client server received that user data and store it into database.

Prerequisites

  1. Express – is a popular framework which is a most common framework used in node.js
  2. express-session – It uses a cookie to store a session id.
  3. passport – It is an authentication middleware for Node.js
  4. passport-google-oauth – It is used to authenticating with Google using OAuth 1.0a and OAuth 2.0..
  5. generate-password – generating random and unique passwords.
  6. Mongoose – It allows you to interact with MongoDB database.
  7. ENV – It allows you to create environment file for keeping configuration variables accessing at any where.
  8. Validator – It helps to validate the user inputs with mongoose.
  9. ejs – It is a templating language that lets you generate HTML markup with plain JavaScript.

Install Packages

npm init
npm install express dotenv express-session generate-password mongoose passport passport-google-oauth ejs --save
npm install nodemon --save-dev

After the installation and create following files and folders in your project directory.

touch index.js
// Root Directory
mkdir oauth
mkdir models
mkdir routes
mkdir views
// Authnetication
cd oauth
touch google.auth.js
// Database
cd models
touch db.js
touch user.model.js
//Routing
cd routes
touch user.route.js
//Views
cd views
mkdir pages
cd pages
touch auth.ejs
success.ejs

Project Setup

Authentication App setup using Oauth2 and Node js

Setup ENV variables

GOOGLE_CLIENT_ID=<!-- PASTE YOUR GOOGLE CLIENT ID -->
GOOGLE_CLIENT_SECRET=<!-- PASTE YOUR GOOGLE CLIENT SECRET -->
CALLBACK_API_URL=http://localhost:3000/oauth2callback
MONGODB_URI=mongodb://localhost:27017/test

Define Mongoose Schema

Before create a schema we have to make an connection with mongodb in /models/db.js file with following contents.

// MongoDB connection
const mongoose = require('mongoose');

mongoose.Promise = global.Promise;
mongoose.connect(process.env.MONGODB_URI, {useUnifiedTopology: true, useNewUrlParser: true, useCreateIndex: true});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error'));

Create schema for users table in /models/user.model.js file with following contents

sid means social network id. Its unique one to identify the user. Its get from google oauth2.0

const mongoose = require('mongoose');
const validator = require('validator');

const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true,
        max: 65
    },
    email: {
        type: String,
        required: true,
        trim: true,
        unique: true,
        lowercase: true,
        max: 65,
        validator: value => {
            if (!validator.isEmail(value)) {
                throw new Error({ error: 'Invalid Email address'  });
            }
        }
    },
    password: {
        type: String,
        required: true,
        trim: true,
        minlength: 8
    },
    provider: {
        type: String,
        required: true,
        trim: true
    },
    sid: {
        type: String,
        required: true,
        trim: true
    }
});

module.exports = mongoose.model('User', userSchema);

In app.js file with following contents

const express = require('express');
const session = require('express-session');
const passport = require('passport');

require('dotenv').config();
require('./models/db');

const routes = require('./routes/user.route');
const app = express();

app.set('view engine', 'ejs');

app.use(session({
  resave: false,
  saveUninitialized: true,
  secret: 'SECRET' 
}));

app.use(passport.initialize());
app.use(passport.session());

app.use('/', routes);

const port = process.env.PORT || 3000;
app.listen(port , () => console.log('App listening on port ' + port));

In views/pages/auth.ejs file with following contents

This is a home page for this application and it contains login button to get user data from google.

<!doctype html>
<html>
<head>
    <title>Google SignIn</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <style>
        body {
        display: -ms-flexbox;
        display: -webkit-box;
        display: flex;
        -ms-flex-align: center;
        -ms-flex-pack: center;
        -webkit-box-align: center;
        align-items: center;
        -webkit-box-pack: center;
        justify-content: center;
        padding-top: 40px;
        padding-bottom: 40px;
        background-color: #f5f5f5;
        }

        .form-signin {
        width: 100%;
        max-width: 500px;
        padding: 15px;
        margin: 0 auto;
        border: 1px solid #ccc;
        }
    </style>
</head>
<body class="text-center">
    <form class="form-signin">
        <h2 class="h3 mb-3 text-primary">Build App with Node.js and OAuth2</h2>
        <h2 class="h3 mb-3 text-primary">Login or Register</h2>
        <a href="/auth/google" class="btn btn-danger"><span class="fa fa-google"></span> SignIn with Google</a>
    </form>
</body>
</html> 
Google oauth login form

In views/pages/success.ejs file with following contents

If you’re logged successful and you will redirect to this page. In this page has user information and logout option.

<!doctype html>
<html>
  <head>
    <title>Google SignIn</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"> <!-- load bootstrap css -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <!-- load fontawesome -->
      <style>
          body        { padding-top:70px; }
      </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
          <div class="col-12">
            <h2 class="text-primary  text-center"><span class="fa fa-user"></span> Profile</h2>
            <p> <strong>Name:</strong>: <%= result.name %></p>
            <a href="/auth/logout">Logout</a>
          </div>
      </div>
    </div>
  </body>
</html> 

In oauth/google.oauth.js file with following contents

Passport strategies for authenticating with Google using ONLY OAuth 2.0.

passport-google-oauth dependency used for authenticate using Google in your Node.js applications.

const passport = require('passport');
const generator = require('generate-password');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
const User = require('../models/user.model');
const AccessInfo = {
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: process.env.CALLBACK_API_URL
};

const verifyCallback = async (accessToken, refreshToken, profile, done) => {
    User.findOne({ sid: profile.id }, function(err, user) {
        if (err) {
          return done(err);
        }
         if (!user) {
            user = new User({
              name: profile.displayName,
              email: profile.emails[0].value,
              password: generator.generate({ length: 10, numbers: true }),
              provider: 'google',
              sid: profile.id
            });
            user.save(function(err) {
              if (err) console.log(err);
              return done(err, user);
            });
        } else {
            //found user. Return
            return done(err, user);
        }
    });
};

passport.use(new GoogleStrategy(AccessInfo, verifyCallback));

passport.serializeUser((user, done) => {
    done(null, user.id);
});

passport.deserializeUser((id, done) => {
    User.findById(id).then(user => {
      done(null, user);
    });
});

In routes/user.route.js file with following contents

Use passport.authenticate(), specifying the 'google' strategy, to authenticate requests.

const router = require('express').Router();
const passport = require('passport');
const googleoAuth = require('../oauth/google.oauth');
const User = require('../models/user.model');

function isUserAuthenticated(req, res, next) {
    if (req.user) {
        next();
    } else {
        res.redirect('/');
    }
}

router.get('/', function(req, res) {
    res.render('pages/auth');
});

router.get('/auth/google', passport.authenticate('google', { scope : ['profile', 'email'] }));
 
router.get('/oauth2callback', passport.authenticate('google', { failureRedirect: '/error' }),
    function(req, res) {
        // Successful authentication, redirect success.
        res.redirect('/user/account');
    }
);

router.get('/user/account', isUserAuthenticated, (req, res) => {
    if (req.user.id) {
        User.findById(req.user.id, function (err, user) {
            if (err) {
                console.log("ERROR" + err)
            }
            res.render('pages/success', { result: user });
        });
    } else {
        return res.status(201).json({message: "Invalid access"});
    }
});

router.get('/error', (req, res) => res.send("error logging in"));

router.get("/auth/google/redirect",passport.authenticate("google", { scope: ['profile', 'email'] }),(req,res)=>{
    res.send(req.user);
    res.send("you reached the redirect URI");
});

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

module.exports = router;

Click here to Get Full Source Code from Github

Click here to read more about articles in node.js

Share Your Thoughts

phpexpertise

I’m Blogger and Programming Blog, Tutorials, PHP, MySQL, jQuery, Laravel, Wordpress and Codeigniter