node.js, express, mongo, and passport

Building on the tutorial from http://cwbuecheler.com/web/tutorials/2013/node-express-mongo/ I wanted to add passport support to allow basic authentication to the site and there seemed to be a few too many conflicting and out of date instructions for what should have been a really simple process.

The objective is local username/password authentication and as we are doing that we might as well be good and store a password hash not the actual password in the db. This could easily be extended to be properly salted etc.


The extra modules we are using that need to be added to package.json are

passport": "*",
"passport-local": "*",
"crypto": "*",
"connect-flash": "*"

then of cause you need to issue a

npm --install

to drag in those extra modules

back in app.js we need to add the following at the top to require the correct modules

Right at the top before you require express

var flash = require('connect-flash'); //flash now should be before express and passport

then you also want to add at the end of the require list

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

Passport setup

To use basic local username passwords with passport you need the following code, the bits that were missing from many examples were the passport.serializeUser() and passport.deserializeUser() functions. In our setup we also reused the usercollection database in mongo called users that contains username and added a password hash and we are using SHA1 as the hash function (passport supports other hashes as well). This particular example takes the password hashes it then checks against the hash stored in the database. An improvement would be to send the hash in the post request instead of the plain text.

If you want to add a user to the mongo db then using the mongo console

use nodetest1
db.usercollection.insert({ "username" : "testuser1", "email" : "testuser1@testdomain.com", "hash" : "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"})

NB the hash is for the password test, so when we come to test this later username testuser1 password test

And this is the extra app.js code to setup passport.

var Users = db.get('usercollection');

passport.use(new LocalStrategy(function(username, password,done){
    Users.findOne({ username : username},function(err,user){
        if(err) { return done(err); }
        if(!user){
            return done(null, false, { message: 'Incorrect username.' });
        }

	var hash = crypto.createHash('sha1');
	hash.update(password);
	var result = hash.digest('hex') ;
		   
        if (result == user.hash) 
        {
        	return done(null, user._id);
        }
        else
        {
            done(null, false, { message: 'Incorrect password.' });
        }  
    });
}));

passport.serializeUser(function(user, done) {
    done(null, user);
});

passport.deserializeUser(function(id, done) {
            Users.findById(id, function(err,user){        
                if(err){done(err);}
                	done(null,user);
            });
});

Now to hook in the passport to the flow in the app.use section before app.use(app.router); add

app.use(flash()); // this must be before passport setup
app.use(passport.initialize());
app.use(passport.session());

Ok cool passport is basically hooked in now so the remaining things are we need a username/password form a way to handle the post request when submitting that form and to prevent unauthorised access to special logged in only user pages. In this example we restrict access to /helloworld and add a login page called /login


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

app.get('/helloworld', ensureAuthenticated, routes.helloworld);
app.get('/login', routes.login);
app.post('/login',
  passport.authenticate('local', { successRedirect: '/helloworld',
                                   failureRedirect: '/login',
                                   failureFlash: true })
);

Adding the ensureAuthenticated will ensure the user is authenticated or redirect to the login page. So thats app.js complete now to add the routes in routes/index.js

All we need to add here is a handler to show the login form view

exports.login = function(req, res){
  res.render('login', { title: 'Login' });
};

Now to add the view in views/login.jade

extends layout

block content
  h1= title
  form#formlogin(name="adduser",method="post",action="/login")
      p 	
            span(class=['input']) Username:
            input#inputName(type="text", placeholder="username", name="username")
      p 	
            span(class=['input']) Password:
            input#inputName(type="password", placeholder="password", name="password")
      
      button#btnSubmit(type="submit") submit

passport expects username and password to be sent in fields called “username” and “password” and these get hooked through to the passport.use(new LocalStrategy(function(username, password,done){} back in app.js

Leave a Reply