Authenticating Users in a Node App using Express & Passport (Part One)

22 November, 2015
post-banner

In this article we are going to build a Node app from scratch. The app will allow a user to be authenticated locally and save their details to a database. This article will be 'Part 1' of a two-part tutorial. In the second part we will add third-party 0social network authentication to the app through Facebook, Twitter and Google+ accounts.

I will be using the Express framework for Node and a popular authentication middleware service called Passport, as well as MongoDB to store the user data.

Getting Started

You will need to have Node, with npm, installed globally on your machine, which you can easily do via the downloads page on the Node website. You will also need a Mongo instance running. To get MongoDB setup and ready to use, you can read my article on getting started with MongoDB.

Express has a great tool for quickly generating Node apps, which you will need to install globally.

$ npm install express-generator -g

Once the Express generator is installed, we can now use it to generate a new Node app. For this article, I am going to use EJS, Embedded JavaScript, as the templating language for the app. We can specify the template language we want to use when we generate the express app, which will save us time later on.

$ express --ejs express-authentication
$ cd express-authentication
$ npm install

This has created a new app called 'express-authentication'. We then changed directory into the 'express-authentication' folder and ran npm install to install all the package dependencies locally.

Now you will be able to run the generated project with the following command:

$ DEBUG=express-authentication:* npm start

Open up http://localhost:3000 in your browser to open the express app.

express-generator

Setup packages

We now have a Node app running with the Express framework and EJS templates ready to use. Before we begin implementing the authentication app, we should install the packages we will need.

$ npm install passport --save
$ npm install passport-local --save
$ npm install mongoose --save
$ npm install bcrypt-nodejs --save
$ npm install connect-flash --save
$ npm install express-session --save

Adding the --save command will automatically add each dependency to our package.json file. The passport and passport-local packages will allow us to authenticate a user locally. Mongoose is a package that allows us to create MongoDB models in Node apps easily. Bcrypt-nodejs us the package we will use to generate a secret hash of the users password. Connect-flash is a pretty cool package to be able to store and display flash messages to the user, for example, we can inform the user if they have entered the wrong password. And express-session is a simple session middleware package for Express apps.

Building the Server

Now that we have the packages ready, we can start to implement the server for our app. As you will see, the express generator has created an app.js file in the root of the directory. We will implement our server here, so let's open it up and start to modify it.

At the top of the file are some variables already required by the express-generator, some routes are set, the templating language is set and some error handlers are defined.

We are going to add variables for the new packages, allow the app to use them and define a new listener on port 3000.

var express = require('express');  
var path = require('path');  
var favicon = require('serve-favicon');  
var logger = require('morgan');  
var cookieParser = require('cookie-parser');  
var bodyParser = require('body-parser');

var routes = require('./routes/index');  
var users = require('./routes/users');

var port = process.env.PORT || 3000;

var passport = require('passport');  
var LocalStrategy = require('passport-local').Strategy;  
var mongoose = require('mongoose');  
var flash = require('connect-flash');  
var session = require('express-session');

// var configDB = require('./config/database.js');
// mongoose.connect(configDB.url);

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));  
app.set('view engine', 'ejs');

app.use(logger('dev'));  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: false }));  
app.use(cookieParser());  
app.use(express.static(path.join(__dirname, 'public')));

app.use(session({ secret: 'shhsecret' }));  
app.use(passport.initialize());  
app.use(passport.session());  
app.use(flash());

app.use('/', routes);  
app.use('/users', users);

// require('./config/passport')(passport);

app.use(function(req, res, next) {  
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});
if (app.get('env') === 'development') {  
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err,
    });
  });
}
app.use(function(err, req, res, next) {  
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {},
  });
});

app.listen(port);  
module.exports = app;  

So this is the full code for the new server. Now if you open the terminal, you are able to run the app by simply entering node app.

There are a couple of commented lines in the code (database config and passport config) that we will uncomment once the necessary files have been created.

$ node app

Database Configuration

The MongoDB database needs to be configured so that we can allow users to successfully authenticate to the app, and to save their details to the database so they are able to log back in with the same credentials. Let's create a new folder called config and create a new JavaScript file called database.js.

$ mkdir config
$ touch config/database.js

Now let's open up config/database.js and set the url for our MongoDB database.

module.exports = {  
  url: 'mongodb://localhost/expressauth',
};

This is all the configuration needed for the database. We are simply setting the url and naming our MongoDB database 'expressauth', which will create a new database if it doesn't already exist.

Now we are able to create a user model which will contain the schema for the 'user' document in the database. Let's create a new folder called models on the apps root and a new file called user.js.

$ mkdir models
$ touch models/user.js

Now we need to implement a user schema.

var mongoose = require('mongoose');  
var bcrypt   = require('bcrypt-nodejs');

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

userSchema.methods.generateHash = function(password) {  
  return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
userSchema.methods.validPassword = function(password) {  
  return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('User', userSchema);  

We have required the mongoose and bcrypt packages to the model. Next we set the schema for a user, which will take in the users email and password as well as a unique ID value set by MongoDB.

The methods at the bottom of the file will generate a hash of the users password for security, using the bcrypt-nodejs package.

Views

The views are the apps markup, which is the interface the user will be displayed with. Two .ejs files should already be in the views directory, error.ejs and index.ejs. Create more views for the login, signup and profile pages.

$ touch views/login.ejs
$ touch views/signup.ejs
$ touch views/profile.ejs

It's a good idea to move the code from the head element into a 'partial' file. This is so that the head can be included in each file, without having to be fully written each time.

$ mkdir views/partials
$ touch views/partials/head.ejs

Inside the partials/head.ejs file add the following code.

<!DOCTYPE html>  
<html>  
  <head>
    <title>Express Authentication</title>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
    <style>
      body { padding-top: 50px; }
      img { width: 140px; height: 140px; }
      .fa-sign-out { float: right; }
    </style>
  </head>

Here we are adding Bootstrap and Font-Awesome to the app for the styling and I am setting some simple CSS in the head for simplicity.

Index.ejs

We can now edit the views/index.ejs file to contain the buttons to allow the user to sign up or log in.

<% include partials/head %>  
  <body>
    <div class="col-sm-8 col-sm-offset-2">
      <div class="jumbotron text-center">
        <h1>Express Authentication</h1>
        <p class="lead">A simple authentication app built with express, node & passport.</p>
        <a href="/login" class="btn btn-default"><span class="fa fa-sign-in"></span> Login</a>
        <a href="/signup" class="btn btn-default"><span class="fa fa-user"></span> Signup</a>
    </div>
  </body>
</html>  

This simple markup will display the heading and two buttons which, so far, will redirect to an error message. The code for the head is included by using the ejs helper <% include partials/head %>.

If you head to http://localhost:3000 once again, you will see the new index page.

index

The login, signup, profile and error pages need to be marked up next. Once the routing is fully set up, the user will be able to navigate through the app by rendering the specific page they need.

For brevity I have added the code for the other pages to GitHub Gists. You can get the markup by opening the following links:

Routes

At the moment, if you click the login or signup button you will be displayed with the error message 404. This is because we haven't set up the routes for the app.

Open the routes/index.js file to set these up.

var express = require('express');  
var passport = require('passport');  
var router = express.Router();

router.get('/', function(req, res, next) {  
  res.render('index', { title: 'Express' });
});

router.get('/login', function(req, res, next) {  
  res.render('login.ejs', { message: req.flash('loginMessage') });
});

router.get('/signup', function(req, res) {  
  res.render('signup.ejs', { message: req.flash('signupMessage') });
});

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

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

module.exports = router;

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

In this file we are setting up the router to specify the views that should be displayed at the specified route. You can also see that the profile.ejs requires the isLoggedIn variable to be true in order for this view to be rendered. The isLoggedIn() function will set this value to true if the user is authenticated.

Now if you open the app and click the login button, you will see a login form as expected.

login

The only problem now is that if you try to sign up, you will be displayed with an error message. This is because we haven't configured Passport yet.

Configuring Passport

Let's create a new file called passport.js in the config/ directory.

$ touch config/passport.js

Inside this file is where we are going to define the 'strategies' of authenticating users to our app. For now we are going to configure a local signup and a local login of a user by defining the passport strategies for this.

var LocalStrategy = require('passport-local').Strategy;  
var User = require('../models/user');

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',
    passwordField: 'password',
    passReqToCallback: true,
  },
  function(req, email, password, done) {
    process.nextTick(function() {
      User.findOne({ 'local.email':  email }, function(err, user) {
        if (err)
            return done(err);
        if (user) {
          return done(null, false, req.flash('signupMessage', 'That email is already in use.'));
        } else {
          var newUser = new User();
          newUser.local.email = 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) {
    User.findOne({ 'local.email':  email }, function(err, user) {
      if (err)
          return done(err);
      if (!user)
          return done(null, false, req.flash('loginMessage', 'No user found.'));
      if (!user.validPassword(password))
          return done(null, false, req.flash('loginMessage', 'Wrong password.'));
      return done(null, user);
    });
  }));
};

With this strategy in place, we are almost ready to allow users to signup to the app.

If you open the routes/index.js file once again, we can add two post routes to allow the user to enter their credentials to our form.

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

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

module.exports = router;  

The last thing to do now is to uncomment the configDB and passport variables from our server. Open app.js and uncomment the following lines of code.

...
var configDB = require('./config/database.js');  
mongoose.connect(configDB.url);  
...
require('./config/passport')(passport);  
...

Authenticating a User

Now the application is at a stage where a user can successfully signup, get redirected to their profile, log out and then log back in if they wish.

signup

The user profile will display their unique ID, email and a Gravatar if they have one associated with their email account.

profile

You can check that the user information was saved to the database successfully by opening a mongo shell and typing the following commands.

> use expressauth
> db.users.find()
{ "_id" : ObjectId("56537c6c6b6b3fe340f81896"), "local" : { "password" : "XXXXXXXXXXXXXXX", "email" : "[email protected]" }, "__v" : 0 }

To be continued...

You can find the code for this project on my GitHub repository.

Next week I will publish the second part of this article. In that article I will add strategies for users to be able to authenticate using their Facebook, Twitter or Google+ accounts.

comments powered by Disqus