Passport integration

In this section we will be integrating passport authentication.

Passport is simple, unobtrusive authentication for Node.js

With Passport Authentication strategies are broken into separate modules. There are modules for all of the popular third party providers; Facebook, twitter, Google, etc. In this section we will be incorporating the local strategy. This is a strategy that takes a username and password and returns an authenticated user. We will be replacing the username with email.

For more information on the passport-local strategy please see.

  1. To begin add Passport and passport-local to your package.json file

    package.json

       "passport": "0.1.x",
       "passport-local": "0.1.x" ,
    
  2. Updated your dependencies

     npm install
    
  3. Lets write some test.

    test/auth/routes.js

     'use strict';
    
     // import the moongoose helper utilities
     var utils = require('../utils');
    
     // import our express app. We will be passing this to supertest
     var app = require('../../app').app;
     var should = require('should');
     var request = require('supertest');
     var User = require('../../users/models').User;
    
     describe('Passport: routes', function () {
    
       var baseUrl = '/auth/local';
       var emailAddress = 'berry@example.com';
       var realPassword = 'secret1';
    
       // This function will run before each test.
       beforeEach(function (done) {
         // TODO this should be refactored into a User.new() function.
         // Hash the password
         User.hashPassword(realPassword, function (err, passwordHash) {
           // Create a User
           var u = {
             passwordHash: passwordHash,
             emails: [
               {
                 value: emailAddress
               }
             ]
           };
           User.create(u, function (err, u) {
             // call the done() method so the mocha knows we are done.
             done();
           });
         });
       });
    
       describe('POST /auth/local', function () {
         it('should redirect to "/account" if authentication fails', function (done) {
           // post is what we will be sending to the /auth/local
           var post = {
             email: 'berry@example.com',
             password: realPassword
           };
           request(app)
             .post(baseUrl)
             .send(post)
             .expect(302)
             .end(function (err, res) {
               should.not.exist(err);
               // confirm the redirect
               res.header.location.should.include('/account');
               done();
             });
         });
         it('should redirect to "/login" if authentication fails', function (done) {
           var post = {
             email: 'berry@example.com',
             password: 'fakepassword'
           };
           request(app)
             .post(baseUrl)
             .send(post)
             .expect(302)
             .end(function (err, res) {
               should.not.exist(err);
               // confirm the redirect
               res.header.location.should.include('/login');
               done();
             });
         });
       });
    
     });
    

    Our tests are now failing.

       ✖ 2 of 11 tests failed:
    
     1) Passport: routes POST /auth/local should redirect to "/account" if authentication fails:
        AssertionError: expected [Error: expected 302 "Moved Temporarily", got 404 "Not Found"] to not exist
    
     2) Passport: routes POST /auth/local should redirect to "/login" if authentication fails:
        AssertionError: expected [Error: expected 302 "Moved Temporarily", got 404 "Not Found"] to not exist
    
  4. Add Passport support to app.js and the POST /auth/local route

    app.js

     //... previous imports
     var auth = require('./auth/routes');
     var passport = require('passport');
    
     var app = exports.app = express();
    
     app.configure(function () {
       //... previous config
       // Add passport support
       // Important: This must come before the app.router middleware
       app.use(passport.initialize());
       app.use(passport.session());
       app.use(app.router);
       app.use(express.static(path.join(__dirname, 'public')));
     });
    
     //... previous routes
    
     app.post('/auth/local', auth.local);
    
     //... previous code
    
  5. Create the route handler:

    We are doing quite a bit here. First we are telling Passport how to serialize and deserialize our user. Passport will create a cookie to store the user.id this is convenient when you are using more then one strategy

    In the next section we are telling Passport about our local strategies, we are changing the usernameField from 'username' to 'email', and we are looking up the user by the email. If one is not found we return 'Unknown email address ...'. If one is found we verify that the provided password matches. Note the function that we are returning. This is the same function that will be called in the next section.

    In the final section we are overriding the authentication function. If you notice the passed function has the same argument as the function that we returned in the previous section. Here we have an opportunity to return something to the user. If thing succeeds successfully we redirect the user to '/account' if there is an error. We redirect them back to '/login'.

    That's it. All of these are standard Passport settings

    auth/routes.js

    
     'use strict';
    
     // auth/local provides routes for POST authentication.
    
     var passport = require('passport');
     var User = require('../users/models').User;
     var LocalStrategy = require('passport-local').Strategy;
    
     // Passport session setup.
     //   To support persistent login sessions, Passport needs to be able to
     //   serialize users into and deserialize users out of the session.  Typically,
     //   this will be as simple as storing the user ID when serializing, and finding
     //   the user by ID when deserializing.
     passport.serializeUser(function (user, fn) {
       fn(null, user.id);
     });
    
     // deserializeUser is passed a function that will return the user the
     // belongs to an id.
     passport.deserializeUser(function (id, fn) {
       User.findOne({_id: id}, function (err, user) {
         fn(err, user);
       });
     });
    
     // Use the LocalStrategy within Passport.
     //   Strategies in passport require a `verify` function, which accept
     //   credentials (in this case, a username and password), and invoke a callback
     //   with a user object.  In the real world, this would query a database;
     //   however, in this example we are using a baked-in set of users.
     passport.use(new LocalStrategy(
       // We are using email for verification so we set the name of
       // usernameField to 'email'. This allows use to post: email and
       // password
       { usernameField: 'email' },
       // This is the callback function it will be passed the email and
       // password that have been submitted.
       function (email, password, fn) {
         // We need to look up the user by email address
         User.findOne({'emails.value': email}, function (err, usr) {
           if (err) {
             return fn(err, false, { message: 'An Error occurred' });
           }
           // no user then an account was not found for that email address
           if (!usr) {
             return fn(err, false, { message: 'Unknown email address ' + email });
           }
           // If we have a user lets compare the provided password with the
           // user's passwordHash
           User.comparePasswordAndHash(password, usr.passwordHash, function (err, valid) {
             if (err) {
               return fn(err);
             }
             // if the password is invalid return that 'Invalid Password' to
             // the user
             if (!valid) {
               return fn(null, false, { message: 'Invalid Password' });
             }
             return fn(err, usr);
           });
         });
       }
     ));
    
     // POST */auth/local
     exports.local = function (req, res, fn) {
       passport.authenticate('local', function (err, user, info) {
         if (err) {
           return fn(err);
         }
         if (!user) {
           return res.redirect('/login');
         }
    
         req.logIn(user, function (err) {
           if (err) {
             return fn(err);
           }
           return res.redirect('/account');
         });
       })(req, res, fn);
     };
    

    Now if we check our test runner we will see that all of our test are passing:

     addition
       ✓ should add 1+1 correctly
       ◦ should return 2 given the url /add/1/1: GET /add/1/1 200 3ms - 1
       ✓ should return 2 given the url /add/1/1
    
     Passport: routes
       POST /auth/local
         ◦ should redirect to "/account" if authentication fails: POST /auth/local 302 7ms - 58
         ✓ should redirect to "/account" if authentication fails
         ◦ should redirect to "/login" if authentication fails: POST /auth/local 302 14ms - 56
         ✓ should redirect to "/login" if authentication fails
    
     Users: models
       #create()
         ✓ should create a new User
       #hashPassoword()
         ✓ should return a hashed password asynchronously
       #comparePasswordAndHash()
         ✓ should return true if password is valid
         ✓ should return false if password is invalid
    
     Users: routes
       POST /signup
         ◦ should redirect to "/account" if the form is valid: POST /signup 302 7ms - 58
         ✓ should redirect to "/account" if the form is valid
         ◦ should redirect to "/login" if the form is invalid: POST /signup 302 1ms - 57
         ✓ should redirect to "/login" if the form is invalid
         ◦ should create a new User if the form is valid: POST /signup 302 12ms - 58
         ✓ should create a new User if the form is valid
    
     ✔ 11 tests complete (356 ms)
    

Resources

  • Passport - is simple, unobtrusive authentication for Node.js
  • passport-local - username and password strategy.
  • supertest - Super-agent driven library for testing node.js HTTP servers using a fluent API

Comments