Local User Authentication With Passport And Express 4

User Authentication Cover

Setting up user authentication can be a tricky business. But fret not, I’ve got you covered! In this tutorial, I’ll show you how to set up your own user authentication from scratch with Passport.js and Express 4, specifically implementing the local strategy with Mongoose and MongoDB.

How it works

Before we dive right into the code, let’s take a minute to explain what we’re exactly doing. I mean, the goal of this article is to make you smarter, not more confused.

First of all, what do we mean by “local strategy”? Well, Passport.js offers many login strategies, such as social media logins. However, the most common and simple strategy of them all, and the one we are considering in this tutorial, is using good ol’ login with a username and passport.

The great thing about Passport.js is that most of the user authentication process is already taken care off. Nevertheless, we will still remain responsible for:

  • Storing and authenticating any user information with Mongoose in our database. Fortunately, there’s a library that greatly simplifies this process.
  • The sessions and cookies, so users don’t have to login every time they visit our page.

Setting up our Express app

To begin this tutorial, you’ll first need Express 4 up and running. You can generate an Express app with the following command line in your project folder.

$ express <express-app-name>

Subsequently, we need install all our dependencies with $ npm install and make sure that everything works fine with $ npm run start. You can find the app running by default on http://localhost:3000/.

Installing our MongoDB database

Install the database with

$ npm install mongodb --save

and launch it with

$ mongod

Side note — If you find yourself running into the error Failed to set up listener: SocketException: Address already in use, run $ killall mongod once before.

Hint: Always make sure to start up your database before your backend application!

Installing the right dependencies

Alright, it’s about time we get to the interesting bits! ๐Ÿคฉ

The Mongoose ODM is the first library we’ll need to install after setting up our basic project template.

$ npm install mongoose --save

For Passport.js, on the other hand, we will need to install several dependencies.

$ npm install passport passport-local passport-local-mongoose --save

And lastly, to handle sessions and cookies we require express-session.

$ npm install express-session --save

From the terminal, we next move on to our app.js file and include the following changes

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

Easy so far, ain’t it? ๐Ÿ‘

Oh! And before I forget it, we also need to connect our app to our database.

mongoose.connect('mongodb://localhost/<database-name>', { useNewUrlParser: true });

Including middleware

Our next step is to include any necessary middleware, which we certainly got a few of. Starting with express-session:

app.use(session({
  name: 'session-id',
  secret: '123-456-789',
  saveUninitialized: false,
  resave: false
}));

Immediately afterwards, we initialise our passport module and connect it to our session module.

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

Last but not least, we configure our Passport/Passport-Local modules using passport-local-mongoose.

passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Creating our User model

If you’ve paid any particular attention to the Passport/Passport-Local configuration code, you’ll notice that it relies on a certain User module. This module is essentially our Mongoose user schema with which we sign up and login all our users.

Let’s continue and create a file called user.js in a folder called models. Our file will contain the following.

var mongoose = require('mongoose');
var passportLocalMongoose = require('passport-local-mongoose');

var UserSchema = new mongoose.Schema({
  username: {
    type: String,
    unique: true,
    required: true
  }
});

UserSchema.plugin(passportLocalMongoose);

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

In our user model, we don’t need to explicitly define a password or even a username as it is automatically taken care of by passport-local-mongoose. Pretty neat, right? However, in this example, I took the liberty of adding some requirements to the username. And in case you were worried about security, our library also takes care of this by hashing and salting our passwords. Double neat, right? ๐Ÿ˜ฒ

After doing this, we have to require our model into our app.js file in order to ensure our Passport/Passport-Local configurations work properly.

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

Configuring our routes

Still awake? Don’t worry, we’re almost done! ๐Ÿคž

Moving on, let’s open up the existing users.js file in the routes folder and include the following changes.

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

router.post('/signup', (req, res, next) => {
  User.register(new User({
      username: req.body.username
    }),
    req.body.password, (err, user) => {
      if (err) {
        res.statusCode = 500;
        res.setHeader('Content-Type', 'application/json');
        res.json({
          err: err
        });
      } else {
        passport.authenticate('local')(req, res, () => {
          User.findOne({
            username: req.body.username
          }, (err, person) => {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'application/json');
            res.json({
              success: true,
              status: 'Registration Successful!',
            });
          });
        })
      }
    })
});

In this chunk of code, we’re signing up a user whenever they access /users/signup with a POST request. As I’ve made it a requirement that each username is unique, our database will throw an error whenever we try to register a duplicate, thus never reaching passport.authenticate('local'), which logs in the newly created user.

router.post('/login', passport.authenticate('local'), (req, res) => {
  User.findOne({
    username: req.body.username
  }, (err, person) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'application/json');
    res.json({
      success: true,
      status: 'You are successfully logged in!'
    });
  })
});

Similarly to before, whenever a user accesses /users/login with a POST request, Passport.js takes care of all the bothersome authentication details for us when we reach passport.authenticate('local'). Furthermore, Passport.js automatically creates a cookie and a session for the user logging in.

router.get('/logout', (req, res, next) => {
  if (req.session) {
    req.logout();
    req.session.destroy((err) => {
      if (err) {
        console.log(err);
      } else {
        res.clearCookie('session-id');
        res.json({
          message: 'You are successfully logged out!'
        });
      }
    });
  } else {
    var err = new Error('You are not logged in!');
    err.status = 403;
    next(err);
  }
});

Last but not least, a user can log out by accessing /users/logout with a GET request. We’re using a GET request since the user does not need to send any information to the server side in order to log out.

In this scenario, next to logging out the user with req.logout(), we have to manually destroy a user’s session and clear any cookies. We also provide some error handling should someone try to logout who’s not logged in.

Oof, we’ve finally made it! Why not give your code a try with Postman? ๐Ÿ‘

Leave a Comment

Your email address will not be published. Required fields are marked *