Advertisement
  1. Code
  2. JavaScript
  3. Node

Authenticating Node.js Applications With Passport

Scroll to top

In this tutorial, we will develop a Node.js application from scratch and use the popular authentication middleware Passport to take care of our authentication concerns.

Passport's documentation describes it as a "simple, unobtrusive authentication middleware for Node" and rightly so.

By providing itself as middleware, Passport does an excellent job at separating the other concerns of a web application from its authentication needs. It allows Passport to be easily configured into any Express-based web application, just like we configure other Express middleware such as logging, body-parsing, cookie-parsing, and session-handling.

This tutorial assumes a basic understanding of Node.js and the Express framework to keep the focus on authentication, although we do create a sample Express app from scratch. We'll secure the app by adding routes to it and authenticating some of those routes.

Authentication Strategies

Passport provides us with 500+ authentication mechanisms to choose from. You can authenticate against a local or remote database instance or use the single sign-on using OAuth providers for Facebook, Twitter, Google, etc. to authenticate with your social media accounts.

But don't worry: you don't need to include any strategy that your application does not need. All these strategies are independent of each other and packaged as separate node modules which are not included by default when you install Passport's middleware: npm install passport.

In this tutorial, we will use the Local Authentication Strategy of Passport and authenticate the users against a locally configured MongoDB instance, storing the user details in the database. This strategy lets you authenticate users in your Node.js applications using a username and password.

For using the Local Authentication Strategy, we need to install the passport-local module:

npm install passport-local

But wait: Before you fire up your terminal and start executing these commands, let's start by building an Express app from scratch and adding some routes to it (for login, registration, and home). Then we'll try to add our authentication middleware to it. Note that we will be using Express 4 for the purpose of this tutorial, but with some minor differences Passport works equally well with Express 3.

Setting Up the Application

If you haven't already, then go ahead and install Express by executing the snippet in your terminal:

npm install express --save

You can also install the express-generator with the following code snippet:

npm install -g express-generator

Executing express passport-mongo on the terminal will generate a boilerplate application in your specified directory. The generated application structure should look like this:

Initial Application StructureInitial Application StructureInitial Application Structure

Let's remove some of the default functionality that we won't be using. You can go ahead and delete the users.js route and remove its references from the app.js file.

Adding Project Dependencies

Open up package.json and add the dependencies for the passport and passport-local modules.

1
"passport": "~0.6.0",
2
"passport-local": "~1.0.0"

Since we will be saving the user details in MongoDB, we will use Mongoose as our object data modelling tool. Another way to install and save the dependency to package.json is by executing the code snippet:

1
npm install mongoose --save 

The package.json file should look like this:

Added Mongoose DependenciesAdded Mongoose DependenciesAdded Mongoose Dependencies

Now, install all the dependencies and run the boilerplate application by executing:

npm install && npm start

The above command will install all of the dependencies and start the node server. You can check the basic Express app at http://localhost:3000/, but there's nothing much to see.

Very soon, we are going to change that by creating a full-fledged Express app that:

  1. shows a registration page for a new user
  2. shows the login of a registered user
  3. authenticates the registered user with Passport

Creating a Mongoose Model

Since we will be saving the user details in Mongo, let's create a User Model in Mongoose and save that in models/user.js in our app.

1
var mongoose = require('mongoose');
2
3
module.exports = mongoose.model('User',{
4
        username: String,
5
        password: String,
6
	email: String,
7
	firstName: String,
8
	lastName: String
9
});

Basically, we are creating a Mongoose model, using which we can perform CRUD operations on the underlying database.

Configuring Mongo

If you do not have Mongo installed locally, then I recommend that you use cloud database services such as MongoDB Cloud. Creating a working MongoDB instance is not only free but is just a matter of a few clicks.

After you create a database on one of these services, it will give you a database URI likemongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName> which can be used to perform CRUD operations on the database. It's a good idea to keep the database configuration in a separate file that you can pull up as and when needed. As such, we create a db.js file in the root directory which looks like:

1
module.exports = {
2
  'url' : 'mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>'
3
}

If you're like me, you are using a local Mongo instance. So it's time to start the mongod daemon, and the db.js should look like this:

1
module.exports = {
2
  'url' : 'mongodb://localhost/passport'
3
}

Now we use this configuration in app.js and connect to it using Mongoose APIs:

1
let dbConfig = require('./db.js');
2
let mongoose = require('mongoose');
3
mongoose.connect(dbConfig.url);

Configuring Passport

Passport just provides the mechanism to handle authentication, leaving the onus of implementing session-handling ourselves, and for that we will be using express-session. Open up app.js and paste the code below before configuring the routes:

1
// Configuring Passport

2
var passport = require('passport');
3
var expressSession = require('express-session');
4
app.use(expressSession({
5
  secret: 'mySecretKey',
6
  resave: true,
7
  saveUninitialized: true
8
}));
9
app.use(passport.initialize());
10
app.use(passport.session());
 

This is needed as we want our user sessions to be persistent in nature. Before running the app, we need to install express-session and add it to our dependency list in package.json. To do that, type npm install --save express-session.

Serializing and Deserializing User Instances

Passport also needs to serialize and deserialize user instances from a session store in order to support login sessions, so that every subsequent request will not contain the user credentials. It provides two methods, serializeUser and deserializeUser, for this purpose. The serializeUser function is used to persist a user's data into the session after successful authentication, while a deserializeUser is used to retrieve a user's data from the session.

Create a new folder called passport, and create a file init.js with the following code snippets:

1
var User = require('../models/user');
2
3
module.exports = function(passport){
4
5
    // Passport needs to be able to serialize and deserialize users to support persistent login sessions

6
    passport.serializeUser(function(user, done) {
7
        console.log('serializing user: ', user);
8
        done(null, user._id);
9
    });
10
11
    passport.deserializeUser(function(id, done) {
12
        User.findById(id, function(err, user) {
13
            console.log('deserializing user:',user);
14
            done(err, user);
15
        });
16
    });
17
}

Using Passport Strategies

We will now define Passport's strategies for handling login and signup. Each of them would be an instance of the Local Authentication Strategy of Passport and would be created using the passport.use() function. We use connect-flash to help us with error handling by providing flash messages which can be displayed to the user on error. This can be installed by running npm i connect-flash.

Login Strategy

Create a login.js file in the passport folder. The login strategy looks like this:

1
// passport/login.js

2
var LocalStrategy   = require('passport-local').Strategy;
3
var User = require('../models/user');
4
5
passport.use('login', new LocalStrategy({
6
    passReqToCallback : true
7
  },
8
  function(req, username, password, done) { 
9
    // check in mongo if a user with username exists or not

10
    User.findOne({ 'username' :  username }, 
11
      function(err, user) {
12
        // In case of any error, return using the done method

13
        if (err)
14
          return done(err);
15
        // Username does not exist, log error & redirect back

16
        if (!user){
17
          console.log('User Not Found with username '+username);
18
          return done(null, false, 
19
                req.flash('message', 'User Not found.'));                 
20
        }
21
        // User exists but wrong password, log the error 

22
        if (!isValidPassword(user, password)){
23
          console.log('Invalid Password');
24
          return done(null, false, 
25
              req.flash('message', 'Invalid Password'));
26
        }
27
        // User and password both match, return user from 

28
        // done method which will be treated like success

29
        return done(null, user);
30
      }
31
    );
32
}));

The first parameter to passport.use() is the name of the strategy which will be used to identify this strategy when applied later. The second parameter is the type of strategy that you want to create; here we use the username-password or the LocalStrategy. Note that by default the LocalStrategy expects to find the user credentials in the username and password parameters, but it allows us to use any other named parameters as well. The passReqToCallback config variable allows us to access the request object in the callback, thereby enabling us to use any parameter associated with the request.

Next, we use the Mongoose API to find the User in our underlying collection of Users to check if the user is a valid user or not. The last parameter in our callback, done, denotes a useful method with which we could signal success or failure to the Passport module. To specify failure, either the first parameter should contain the error or the second parameter should evaluate to false. To signify success, the first parameter should be null and the second parameter should evaluate to a truthy value, in which case it will be made available on the request object.

Since passwords are inherently weak in nature, we should always encrypt them before saving them to the database. So we'll use bcryptjs to help us with the encryption and decryption of passwords.

In the login.js file, we install bcryptjs by executing the command npm install bcryptjs and adding the following code snippets to the login.js file just below the passport.use() function.

1
var bCrypt = require('bcrypt-nodejs');
2
3
module.exports = function(passport){
4
    
5
    passport.use('login', ...)
6
    );
7
    
8
    var isValidPassword = function(user, password){
9
        return bCrypt.compareSync(password, user.password);
10
    }  
11
}

If you're feeling uneasy with the code snippets and prefer to see the complete code in action, feel free to browse the code here.

Registration Strategy

Now, we create a signup.js file in the passport folder and define the next strategy, which will handle registration of a new user and create an entry in our underlying Mongo database:

1
var LocalStrategy   = require('passport-local').Strategy;
2
var User = require('../models/user');
3
var bCrypt = require('bcryptjs');
4
5
module.exports = function(passport){
6
passport.use('signup', new LocalStrategy({
7
    passReqToCallback : true 
8
  },
9
  function(req, username, password, done) {
10
    findOrCreateUser = function(){
11
      // find a user in Mongo with provided username

12
      User.findOne({'username':username},function(err, user) {
13
        // In case of any error return

14
        if (err){
15
          console.log('Error in SignUp: '+err);
16
          return done(err);
17
        }
18
        // already exists

19
        if (user) {
20
          console.log('User already exists');
21
          return done(null, false, 
22
             req.flash('message','User Already Exists'));
23
        } else {
24
          // if there is no user with that email

25
          // create the user

26
          var newUser = new User();
27
          // set the user's local credentials

28
          newUser.username = username;
29
          newUser.password = createHash(password);
30
          newUser.email = req.param('email');
31
          newUser.firstName = req.param('firstName');
32
          newUser.lastName = req.param('lastName');
33
34
          // save the user

35
          newUser.save(function(err) {
36
            if (err){
37
              console.log('Error in Saving user: '+err);  
38
              throw err;  
39
            }
40
            console.log('User Registration succesful');    
41
            return done(null, newUser);
42
          });
43
        }
44
      });
45
    };
46
    
47
    // Delay the execution of findOrCreateUser and execute 

48
    // the method in the next tick of the event loop

49
    process.nextTick(findOrCreateUser);
50
  }));
51
  
52
  // Generates hash using bCrypt

53
  var createHash = function(password){
54
  return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
55
}};

Here, we again use the Mongoose API to find out if any user with the given username already exists. If not, then create a new user and save the user information in Mongo. Else return the error using the done callback and flash messages. Note that we use bcryptjs to create a hash of the password before saving it.

Creating Routes

If we were to see a bird's-eye view of our application, it would look like this:

Birds Eye View of Our ApplicationBirds Eye View of Our ApplicationBirds Eye View of Our Application

We now define our routes for the application in the following module, which takes the instance of Passport created in app.js above. Save this module in routes/index.js.

1
module.exports = function(passport){
2
3
  /* GET login page. */
4
  router.get('/', function(req, res) {
5
    // Display the Login page with any flash message, if any

6
    res.render('index', { message: req.flash('message') });
7
  });
8
9
  /* Handle Login POST */
10
  router.post('/login', passport.authenticate('login', {
11
	successRedirect: '/home',
12
	failureRedirect: '/',
13
	failureFlash : true  
14
  }));
15
16
  /* GET Registration Page */
17
  router.get('/signup', function(req, res){
18
	res.render('register',{message: req.flash('message')});
19
  });
20
21
  /* Handle Registration POST */
22
  router.post('/signup', passport.authenticate('signup', {
23
	successRedirect: '/home',
24
	failureRedirect: '/signup',
25
	failureFlash : true  
26
  }));
27
28
  return router;
29
}

The most important part of the above code snippet is the use of passport.authenticate() to delegate authentication to the login and signup strategies when an HTTP POST is made to the /login and /signup routes respectively. Note that it is not mandatory to name the strategies on the route path, and they can be named anything.

Creating Jade Views

In the views folder of our application, we should see .jade files. Jade is a templating engine, primarily used for server-side templating in Node.js. It's a powerful way of writing markup and rendering pages dynamically using Express. It gives a lot more flexibility compared to using a static HTML file. To learn more about Jade and how it works, you can check out the documentation.

Next, we create the following four views for our application: 

  1. layout.jade contains the basic layout and styling information.
  2. index.jade contains the login page providing the login form and giving the option to create a new account.
  3. register.jade contains the registration form.
  4. home.jade says hello and shows the logged-in user's details.
1
doctype html
2
html
3
  head
4
    title= title
5
    link(rel='stylesheet', href='/stylesheets/style.css')
6
    link(rel='stylesheet', href='http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css')
7
  body
8
    block content

In the index.jade file, we will include the following code snippets:

1
extends layout
2
3
block content
4
  div.container
5
	div.row
6
	  div.col-sm-6.col-md-4.col-md-offset-4
7
		h1.text-center.login-title Sign in to our Passport app
8
		  div.account-wall
9
			img(class='profile-img', src='https://lh5.googleusercontent.com/-b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120')
10
			form(class='form-signin', action='/login', method='POST')
11
			  input(type='text', name='username' class='form-control', placeholder='Email',required, autofocus)
12
			  input(type='password', name='password' class='form-control', placeholder='Password', required)
13
			  button(class='btn btn-lg btn-primary btn-block', type='submit') Sign in
14
			  span.clearfix
15
		  a(href='/signup', class='text-center new-account') Create an account
16
		  #message
17
		  if message
18
			h1.text-center.error-message #{message}

In the register.jade file, we'll include the following code snippets:

1
extends layout
2
3
block content
4
    div.container
5
		div.row
6
			div.col-sm-6.col-md-4.col-md-offset-4
7
				h1.text-center.login-title Registration Details
8
					div.signup-wall
9
						form(class='form-signin', action='/signup', method='POST')
10
							input(type='text', name='username', class='form-control', placeholder='Username',required, autofocus)
11
							input(type='password', name='password', class='form-control nomargin', placeholder='Password', required)
12
							input(type='email', name='email', class='form-control', placeholder='Email',required)
13
							input(type='text', name='firstName', class='form-control', placeholder='First Name',required)
14
							input(type='text', name='lastName', class='form-control', placeholder='Last Name',required)
15
							button(class='btn btn-lg btn-primary btn-block', type='submit') Register
16
							span.clearfix
17
					#message
18
						if message
19
							h1.text-center.error-message #{message}

In the home.jade file, we'll include the following code snippets:

1
extends layout
2
3
block content
4
    div.container
5
		div.row
6
			div.col-sm-6.col-md-4.col-md-offset-4
7
				#user
8
					h1.text-center.login-title Welcome #{user.firstName}. Check your details below:
9
						div.signup-wall
10
							ul.user-details
11
								li Username ---> #{user.username}
12
								li Email    ---> #{user.email}
13
								li First Name ---> #{user.firstName} 
14
								li Last Name ---> #{user.lastName}
15
						a(href='/signout', class='text-center new-account') Sign Out

Now the registration page looks like this:

registration-pageregistration-pageregistration-page

The Login page looks like this:

Login Page for Our Passport AppLogin Page for Our Passport AppLogin Page for Our Passport App

 

And the details page looks like this:

details-pagedetails-pagedetails-page

Implementing Logout Functionality

Passport, being middleware, makes it possible to add certain properties and methods on request and response objects. Passport has a very handy request.logout() method which invalidates the user session apart from other properties.

So it's easy to define a logout route:

1
   /* Handle Logout */
2
    router.get('/signout',  function(req, res, next) {
3
        req.logout(function(err) {
4
        if (err) { return next(err); }
5
        res.redirect('/')
6
        })
7
    });

Protecting Routes

Passport also gives the ability to protect access to a route which is deemed unfit for an anonymous user. This means that if a user tries to access http://localhost:3000/home without authenticating in the application, they will be redirected to the home page.

1
/* GET Home Page */
2
router.get('/home', isAuthenticated, function(req, res){
3
  res.render('home', { user: req.user });
4
});
5
6
// As with any middleware it is quintessential to call next()

7
// if the user is authenticated

8
var isAuthenticated = function (req, res, next) {
9
  if (req.isAuthenticated())
10
	return next();
11
  res.redirect('/');
12
}

Conclusion

Passport is not the only player in this arena when it comes to authenticating Node.js applications, but the modularity, flexibility, community support, and the fact that it's just middleware make Passport a great choice.

For a detailed comparison of Passport and Everyauth, here is an interesting and informative perspective from the developer of Passport himself.

You can find the full source code for the example in our GitHub repo.

If you want to see what else you can do with Node.js, check out the range of Node.js items on Envato Market, from a responsive AJAX contact form to a URL shortener, or even a database CRUD generator.

This post has been updated with contributions from Mary Okosun. Mary is a software developer based in Lagos, Nigeria, with expertise in Node.js, JavaScript, MySQL, and NoSQL technologies.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.