Authentication with PassportJS

Written by Siddhartha Gogoi

16 min read ·

Authentication with Passport

Authentication by definition is the process or action of verifying the identity of a user or process.

User Authentication is a necessary module when we build a membership site or any site that involves logging in or signing up a user. Authentication can be done in many ways and there are many services which provide middlewares to the Authentication process. In our project, we will go with Passport.js.

From the Passport.js website we have:

Passport is an authentication middleware for Node. It is designed to serve a singular purpose: authenticate requests. When writing modules, encapsulation is a virtue, so Passport delegates all other functionality to the application. This separation of concerns keeps code clean and maintainable, and makes Passport extremely easy to integrate into an application.”

Passport provides various “Strategies” to authenticate requests.

What are Strategies?

Strategies are nothing but authentication modules. Each module provides a different authentication method, say Local username and password or OAuth Authentication method. We will get to what OAuth Authentication is later. We will start off with the Local Username and password Strategy.

For the purpose of User Registration and Authentication, we will go with the ‘Local’ Strategy from Passport.

Required Node dependencies before we start

  • express
  • express Sessions
  • cookie Parser
  • bodyParser
  • bcrypt
  • mongodb
  • mongoose
  • passport
  • passport local
  • passport-facebook
  • ejs (view engine)

Once all the modules are installed we go ahead and create a file for us to hold our server code say, server.js

We set the port and import all the modules

var express = require('express');
var app = express();
var port = process.env.PORT || 3000;

var cookieParser = require('cookie-parser');
var session = require('express-session');
var morgan = require('morgan');
var mongoose = require('mongoose');
var bodyParser= require('body-parser');
var flash = require('connect-flash');
var passport = require('passport');

Set the connection to your mongoose database

mongoose.connect('mongodb://localhost/yourDbName');

Use the instances of express sessions and bodyParser

app.use(bodyParser.urlencoded({extended: false}));
  app.use(session({
    secret: 'anystringoftext',
    saveUninitialized: false,
    resave: false
}));

bodyParser.urlencoded({extended: false})

Here, bodyparser.urlencoded takes an optional option extended. From the documentation of bodyParser we have

The extended option allows choosing between parsing the URL-encoded data with the query string library (when false) or the qs library (when true). The “extended” syntax allows for rich objects and arrays to be encoded into the URL-encoded format, allowing for a JSON-like experience with URL-encoded.

Coming to the express-session code that we wrote

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

a session has three options here

(secret, saveUnitialized and resave) From the express-session documentation we have

secret Secret is a required option. This is the secret used to sign the session ID cookie. This can be either a string for a single secret or an array of multiple secrets. If an array of secrets is provided, only the first element will be used to sign the session ID cookie, while all the elements will be considered when verifying the signature in requests.

saveUnitialized:

Forces a session that is “uninitialized” to be saved to the store. A session is uninitialized when it is new but not modified. Choosing false is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. Choosing false will also help with race conditions where a client makes multiple parallel requests without a session.

resave:

Forces the session to be saved back to the session store, even if the session was never modified during the request. Depending on your store this may be necessary, but it can also create race conditions where a client makes two parallel requests to your server and changes made to the session in one request may get overwritten when the other request ends, even if it made no changes (this behavior also depends on what store you’re using). The default value is true, but using the default has been deprecated, as the default will change in the future. Please research into this setting and choose what is appropriate to your use-case. Typically, you’ll want false.

Next, we need to use passport in our server.

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

What passport.initialize() does is that it returns a middleware which must be called at the start of connecting or express based apps. This sets up req.login() and req.logout()

passport.session is used if the app needs persistent login sessions. This returns a middleware which will try to read a user out of the sessions; if one is there, it will store the user in req.user, if not, it will do nothing.

For the login screen to actually be displayed on the browser we need to set a view engine that can render displays on the screen and this can be done with the help of view templates like jade, ejs, handlebars.

For the purpose of this tutorial, we will use ejs.

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

Set the server to listen to a port

app.listen(8080);
console.log("Server running on port"  +  port);

So our server code is done for now and it looks something like this (not the finalized version)

var express = require('express');
var app = express();
var port = process.env.PORT || 3000;

var cookieParser = require('cookie-parser');
var session = require('express-session');
var mongoose = require('mongoose');
var bodyParser= require('body-parser');
var passport = require('passport');

mongoose.connect('mongodb://localhost/yourDbName');

app.use(cookieParser());
app.use(bodyParser.urlencoded({extended: false}));
app.use(session({
    secret: 'anystringoftext',
    saveUninitialized: false,
    resave: false
}));

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

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

app.listen(port);
console.log("Server running on port: " + port);

Alright, now we will go ahead and create a directory for view namely ‘views’. This is where our ejs code will go in. All the pages for login, register, secret/profile pages will be written in ejs and go in this directory.

After the Views folder is created, go ahead and create four pages inside the Views directory.

index.ejs login.ejs profile.ejs signup.ejs

index.ejs code

<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
	<h1>Node Authentication</h1>
	<p>Login or Register with:</p>
	<a href="/login">
		<span>Local Login</span>
	</a>
	<a href="/signup" >Local Signup</a>
	<a href="/auth/facebook" >Login with Facebook </a>
	</div>
</body>
</html>

Login.ejs code

<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
	<form action="/login" method="POST">
            <div>
                <label>Email</label>
                <input type="text"  name="email"> 
            </div>
            <div >
                <label>Password</label>
                <input type="password"  name="password">
            </div>
            <button type="submit">Login</button>
        </form>
	</div>
</body>
</html>

Signup.ejs code

<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
	<form action="/signup" method="POST">
            <div >
                <label>Email</label>
                <input type="text"  name="email">
            </div>
            <div>
                <label>Password</label>
                <input type="password" name="password">
            </div>
            <button type="submit">
                Signup
            </button>
        </form>
	</div>
</body>
</html>

Profile.ejs

<!Doctype html>
<html>
    <head>
        Node Authentication
    </head>
    <body>
        <h1>Profile page</h1>
        <p>
            <strong>id</strong> <% user._id %> <br>
        </p>
    </body>
    </html>

We will leave the css and styling away for now.

Once the view pages are done we will create the routes. Create a folder named app. Inside it create a file named` routes.js

module.exports = function (app, passport) {
    app.get('/', function (req, res) {
        res.render('index.ejs');
    });

    app.get('/login', function(req, res){
        res.render('login.ejs');
    });

    app.post('/login', passport.authenticate('local-login', {
        successRedirect: '/profile',
        failureRedirect: '/login',
        failureFlash: true
    }));

    app.get('/signup', function (req, res) {
        res.render('signup.ejs' )
    });

    app.post('/signup', passport.authenticate('local-signup', {
        successRedirect: '/',
        failureRedirect: '/signup', 
    }));

    app.get('/profile', isLoggedIn, function(req, res){
        res.render('profile.ejs', { user: req.user })
    });
};

function isLoggedIn(req, res, next) {
    if(req.isAuthenticated()){
        return next();
    }
    res.redirect('/login');
}

We will break this code down in to smaller code blocks and try to understand what is going on

app.get('/', function (req, res) {
        res.render('index.ejs');
    });

In this code of block, express renders the index.ejs file on the ’/’ path i.e. the root path of our app. So, whenever our server starts up and we navigate to the port where it is running, index.ejs is rendered and the content is displayed onto our screens.

app.get('/login', function(req, res){
        res.render('login.ejs');
    });

This block of code is pretty similar to our previous code and whenever we navigate to the login path of the port that our server is running, we are displayed the login.ejs file content where we have the login page.

app.post('/login', passport.authenticate('local-login', {
        successRedirect: '/profile',

        failureRedirect: '/login'
}));

Here we see an interesting thing. On our login route we are passing the passport.authenticate function

The syntax of this function is passport.authenticate(strategyName[, options][, callback])

Here strategyName is ‘local-login’, the options are redirects and determines the actions what happens upon the different results of authentication. successRedirect: '/profile' --- this piece of code says that on successful authentication of the user, we would successfully redirect the authenticated user to the profile page failureRedirect: '/login' --- similarly on unsuccessful authentication, we redirect back to the login page

app.get('/signup', function (req, res) {
       res.render('signup.ejs')
   });

This piece of code is pretty self explanatory now. It basically renders the signup.ejs file on the signup route of our server.

app.post('/signup', passport.authenticate('local-signup', {
        successRedirect: '/',
        failureRedirect: '/signup'
    }));

Here, we once again use the passport.authenticate method for our signup action. On successful signup the user credentials would be added to the database and then he would be redirected to the homepage. If the signup fails for some reason the user is then redirected to the signup page.

Next we come to an important piece of code

app.get('/profile', isLoggedIn, function(req, res){
        res.render('profile.ejs', { user: req.user })
    });

This block of code renders our profile page. The thing to keep in mind is that our profile page should be accessible to us (authenticated users) only. That is, it should be secret to us. Also, a person should only be access the profile page only when he is authenticated. In other words, if you are not authenticated, you should not be able to access the profile page. How do we achieve that? We do that with the help of this function.

function isLoggedIn(req, res, next) {
    if(req.isAuthenticated()){
        return next();
    }
    res.redirect('/login');
}

The function isLoggedIn makes it so that if the if the request it receives is authenticated it returns the value true. That is, in simpler words if the user is logged in (authenticated) it will return true. We come back to the previous function.

app.get('/profile', isLoggedIn, function(req, res){
        res.render('profile.ejs', { user: req.user })
    });

since isLogged is passed in as a parameter int the function, all values must return true. If the user is logged in, it returns the next() function. If isLoggedIn returns false, then the function is not executed and we are redirected to the login page.

So, our routes are pretty much done for the local user login and password.

Next, inside the app folder, we will create another folder named Models. Inside the models’ folder, create a file named User.js

var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var userSchema = mongoose.Schema({
    local: {
        username: String,
        password: String
    },
    // Needed for Facebook Authentication
    facebook: {
        id: String,
        token: String,
        email: String,
        name: String
    }
});

userSchema.methods.generateHash = function(password){
    return bcrypt.hashSync(password, bcrypt.genSaltSync(9));
}

userSchema.methods.validPassword = function(password){
    return bcrypt.compareSync(p```a
ssword, this.local.password);
}
module.exports = mongoose.model('User', userSchema);

In the first two lines, we have an important module we need, bc rypt. bcrypt is a password hashing function that is needed to encrypt our passwords.

var userSchema = mongoose.Schema({
    local: {
        username: String,
        password: String
    }
});

userSchema.methods.generateHash = function(password){
    return bcrypt.hashSync(password, bcrypt.genSaltSync(9));
}

userSchema.methods.validPassword = function(password){
    return bcrypt.compareSync(p```a
ssword, this.local.pass```
word);
}
module.exports = mongoose.model('User', userSchema);

In this piece of code

var userSchema = mongoose.Schema({
    local: {
        username: String,
        password: String
    }
});

Here we define the type of input our schema takes. username and password both take strings as their input.

userSchema.methods.generateHash = function(password){
    return bcrypt.hashSync(password, bcrypt.genSaltSync(9));
}

We will talk about what is happening here with respect to bcrypt. Let us check out the parameters involved in the hashing of the password.

hashSync -- From the documentation hashSync(data, salt) data - [REQUIRED] - the data to be encrypted. salt - [REQUIRED] - the salt to be used to hash the password. If specified as a number then a salt will be generated with the specified number of rounds and used (see example under Usage).

So, data = password salt - bcryptgenSaltSync(9)

Diving deeper we have

bcryptgenSaltSync() -- genSaltSync(rounds, minor) rounds - [OPTIONAL] - the cost of processing the data. (default - 10) minor - [OPTIONAL] - minor version of bcrypt to use.

In simpler words, what we are doing in the original version of the code block is that we are hashing the password for a total of 9 rounds.

In this piece of code we have

userSchema.methods.validPassword =
 function(password){
    return bcrypt.compareSync(password, this.local.password);
}

The purpose of this function is to compare the password and see if it is indeed the original password as saved in our database.

compareSync --

compareSync(data, encrypted) data - [REQUIRED] - data to compare. encrypted - [REQUIRED] - data to be compared to.

Comparing this is to our code, we can see that we are comparing the password to the encrypted password we have in our local database.

In the end, we export our code so that it can reuse in other files as a module.

Now, all that is left is to set up the authentication procedure.

Create a folder named config. Inside the folder, we will create a file called passport.js.

The code in the file

var LocalStrategy = require('passport-local').Strategy;
var FacebookStrategy = require('passport-facebook').Strategy;

var User = require('../app/models/user');
var passport = require('passport');

module.exports = function (passport) {
  passport.serializeUser(function (user, done) {
    done(null, user.id);
  });

  passport.deserializeUser(function (id, done) {
    User.findById(id, function (err, user) {
      done(err, user);
    });
  });

  passport.use('local-signup', new LocalStrategy({
    usernameField: 'email',
    passworldField: 'password',
    passReqToCallback: true
  },
    function (req, email, password, done) {
      process.nextTick(function () {
        User.findOne({ 'local.username': email }, function (err, user) {
          if (err)
            return done(err);
          if (user) {
            return done(null, false);
          }
          else {
            var newUser = new User();
            newUser.local.username = email;
            newUser.local.password = newUser.generateHash(password);

            newUser.save(function (err) {
              if (err)
                throw err;
              return done(null, newUser);
            })
          }
        })
      })
    }
  ))
}

passport.use('local-login', new LocalStrategy({
  usernameField: 'email',
  passwordField: 'password',
  passReqToCallback: true
},
  function (req, email, password, done) {
    process.nextTick(function () {
      User.findOne({ 'local.username': email }, function (err, user) {
        if (err)
          return done(err);
        if (!user)
          return done(null, false);
        if (!user.validPassword(password)) {
          return done(null, false);
        }
        return done(null, user);
      })
    })
  }
));

Here we see two important methods.

  1. passport.serializeUser()
  2. passport.deserializeUser()
1. passport.serializeUser(function (user, done) {
    done(null, user.id);
  });

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 = {}. Here, for instance, it would be (as we provide the user id as the key) req.session.passport.user = {id:'xyz'}

2. passport.deserializeUser(function (id, done) {
    User.findById(id, function (err, user) {
      done(err, user);
    });
  });

The first argument of deserializeUser corresponds to the key of the user object that was given to the done function (see 1.). So your whole object is retrieved with help of that key. That key here is the user id (key can be any key of the user object i.e. name, email etc). In deserializeUser that key is matched with the in-memory array/database or any data resource.

The fetched object is attached to the request object as req.user

findById - Finds a single document by its _id field. It is provided by Mongoose.

The sign-up method code block

passport.use('local-signup', new LocalStrategy({
    usernameField: 'email',
    passworldField: 'password',
    passReqToCallback: true
  },
    function (req, email, password, done) {
      process.nextTick(function () {
        User.findOne({ 'local.username': email }, function (err, user) {
          if (err)
            return done(err);
          if (user) {
            return done(null, false);
          }
          else {
            var newUser = new User();
            newUser.local.username = email;
            newUser.local.password = newUser.generateHash(password);

            newUser.save(function (err) {
              if (err)
                throw err;
             return done(null, newUser);
            })
          }
        })
      })
    }
  ))

The user registration is done via this piece of code block.

process.nextTick() - To schedule a callback function to be invoked in the next iteration of the Event Loop, we use process.nextTick(). It just takes a callback with no time bound, since it will be executing in the next iteration of the Event Loop.

The flow of the registration process here goes as such. The user inputs the credentials in the registration form. The form takes the input and checks if there already exists a username with the same email. If the user exists, then the registration process is failed. If there doesn’t exist a user with the same username then a new user is created. The username, which is an email, is saved as local.username and the password is saved as local.password after a hash has been generated on it. This is done so that the password is encrypted and in the probability of someone hacking into the site and stealing the password, the hacker couldn’t steal it. Finally, the user is saved in the database and the user is successfully registered in our app.

passport.use('local-login', new LocalStrategy({
  usernameField: 'email',
  passwordField: 'password',
  passReqToCallback: true
},
  function (req, email, password, done) {
    process.nextTick(function () {
      User.findOne({ 'local.username': email }, function (err, user) {
        if (err)
          return done(err);
        if (!user)
          return done(null, false);
        if (!user.validPassword(password)) {
          return done(null, false);
        }
        return done(null, user);
      })
    })
  }
));

The authentication in the login method is provided here by passport. Here we use the passport local method via LocalStrategy.

User.findOne is basically provided by Mongoose. What is essentially happening here is that we are checking the database if the input email exists or not. If it doesn’t, then the authentication fails. If the user exists, then we check whether the password is valid or not. If it is valid, the authentication process is carried out successfully and the user is redirected to the profile/secret page.

Our Authentication module is thus complete.

Next, we move on to the Facebook Authentication module for our app.

Before setting up the Facebook Login method on our file we need to set up an app on our facebook developers account. Once our facebook app is created we get few important credentials which are needed to set up the facebook authentication module namely, Client Secret, Client ID. Once both these are fetched we wi` ll put them in our app.

Go ahead and create an auth.js file inside our config file.

module.exports = {
    'facebookAuth' : {
        'clientID': 'client ID goes here',
        'clientSecret': 'client Secret goes here',
        'callbackURL': 'http://localhost:yourportnumber/login/facebook/return',
        'profileFields': ['emails', 'displayName'] 
    }
}

profileFields generally ask for extra permissions an app needs to access. In our case, our app needs o access emails and the displayName. Once the credentials are set up, go ahead and paste these two lines of code in the routes.js file

    app.get('/auth/facebook', passport.authenticate('facebook', {scope: ['email']}));
    app.get('/login/facebook/return',
    passport.authenticate('facebook', { successRedirect: '/profile', failureRedirect:` '/' }));

What these two lines of code indicate is that whenever we navigate to the '/auth/facebook' route, we are prompted with a facebook authentication dialog box. After the user is authenticated via facebook, the callback url, which is 'login/facebook/return', redirects us to the profile page on successful authentication or the root path on authentication failure.

Since the logging in and signing in functionality of app is done via both facebook and local username and password all that is left is to log the user out from the app.

Go ahead to our routes.js folder and add this piece of code

router.get('/logout', function(req, res){
    req.session.destroy(function (err) {
        res.redirect('/'); 
      });
});

What is happening here is that when the logout button is called, it destroys the session that was once set at the beginning when our user was authenticated. This logs the user out and the profile page is inaccessible to him.

Head over to the profile.ejs file and add an href tag so that our user can click on it and log out of the app.

Thus a fully functional authentication module is completed with the help of PassportJS.

Enjoyed this post? Receive the next one in your inbox!
Sid
Siddhartha Gogoi

Sid is an aspiring polyglot and likes to read fiction in his free time.

Copyright © 2018 Tech47.