{ Authentication with Passport Local . }

Objectives:

By the end of this chapter, you should be able to:

  • Compare and contrast using passport versus rolling your own authentication structure
  • Explain what a local strategy is
  • Include passport.js in an express application
  • Authenticate an express application using passport-local

Auth with passport

We've seen how to write code for authentication and authorization of users. We've also learned about the concepts underlying password hashing, and seen examples of tools (such as bcrypt) to help with that hashing.

However, it's important to note that there are a number of off-the-shelf tools that can help with authentication and authorizaiton. For Node.js, the most popular library for auth is called passport. Let's take a look at how we can refactor the code we've already written to make use of passport.

First, we'll need to install passport, along with passport-local (we'll explain more about this package below):

npm install --save passport passport-local

Once that is installed, we can add the following code in our routes.

var passport = require("passport");
var session = require("cookie-session");

app.use(session({ secret: 'DO NOT MAKE THIS PUBLIC!' }));
app.use(passport.initialize());
app.use(passport.session());

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

That's all we need for our routing logic to get this working! But here comes the fun part: setting up passport!

The process for authentication with passport involves the following:

  1. Select a strategy. A strategy is a means or process of authenticating. A local strategy is one that involves a username and password, whereas there are many external strategies for authenticating with other providers (Facebook, Google, Twitter, etc.).
  2. Create a new object from that strategy's constructor function and as a first parameter to the constructor function, pass in an optional object with options for configuration
  3. Set up the second parameter to the callback function which is known as the verify callback.
  4. Depending on the result of the verify callback, the failureRedirect route from above will be requested or the user will be "serialized" (their information will be stored in the session, marking them as logged in) and the successRedirect will be requested.

Let's examine this code with quite a few comments:

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

passport.use(new LocalStrategy(
    // the first parameter is an optional object with options
{
    // when using the local strategy you MUST name your keys usernameField and passwordField. By default they will have values of "username" and "password", but if you are using something like an email instead of a username or have different name attribute values in your form, modifying the optional object is essential for authentication to work.
    usernameField: 'email',
    passwordField: 'password',
    // by default this option is set to false, but when specified to true, the first parameter of the verify callback will be the request object. This is quite useful if you want to see if your application has multiple strategies and you want to see if a user is already logged in with an existing strategy, if they are you can simply associate the new strategy with them (eg. they have put in their username/password, but then try to authenticate again through twitter)
    passReqToCallback: true,
},
  // the second parameter to the constructor function is known as the verify callback. Since we have set passReqToCallback as true, the first parameter is the request object. The second parameter is the username which comes from user entered data in a form, the third second parameter is the plain text password which comes from user entered data in a form. The fourth parameter is a callback function that will be invoked depending on the result of the verify callback.
  function verifyCallback(req, username, password, done) {
    // find a user in the database based on their username  
    db.User.findOne({ username: username }, function (err, user) {
      // if there is an error with the DB connection (NOT related to finding the user successfully or not, return the callback with the error)
      if (err) return done(err);
      // if the user is not found in the database or if the password is not valid, do not return an error (set the first parameter to the done callback as null), but do set the second parameter to be false so that the failureRedirect will be reached. 

      // validPassword is a method WE have to create for every object created from our Mongoose model (we call these instance methods or "methods" in Mongoose)
      if (!user || !user.validPassword(password)) {
        return done(null, false);
      }
      // if the user has put in the correct username and password, move onto the next step and serialize! Pass into the serialization function as the first parameter the user who was successfull found (we will need it's id to store in the session and cookie)
      return done(null, user);
    });
  }
));

// this code is ONLY run if the verify callback returns the done callback with no errors and a truthy value as the second parameter. This code only runs once per session and runs a callback function which we can assume will not have any errors (null as the first parameter) and the data we want to put in the session (only the user.id). The successCallback is run next!
passport.serializeUser(function(user, done) {
  done(null, user.id);
});

// once a user has been authenticated and serialized, we now find that user in the database on every request. This allows passport to have some useful methods on the request object like req.user (the current user logged in) and req.isAuthenticated() (returns true if the user is logged in or false if not)
passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

Helpful methods

When passport is successfully set up, we are given access to a few properties/methods on the req object, including:

isAuthenticated - a boolean which will be true if the user has been authenticated or false if not

user - an object containing the user's information (what is retrieved from deserializing the user)

login (also called logIn) - a method for serializing a user right away (this is helpful if you do not want to directly use the passport.authenticate method or if you want to log in a user right after creating them)

logout - a method for removing the session information for the logged in user.

You can see the definition of these methods here

Logging in right after signup

app.post('/signup', function(req, res, next){
    db.User.create(req.body.user).then(function(user){
        // jump straight to the serialization process using req.logIn()
        req.logIn(user, function(err) {
            return res.redirect(`/users/${user.id}`);
        });        
    }, function(err){
        return next(err); 
    });
})

Flash Messages with Passport

To render the flash messages we can either add an object in the done callback called message like what you see here or we can directly add the failure and success flash message.

passport.authenticate('local', { 
  failureFlash: 'Invalid username or password.',
  successFlash: 'Logged In!' 
});

Sample App

You can find a simple passport-local application here.

When you're ready, move on to OAuth with Passport.js

Continue