Aiming for elegance, one thought at a time

Setting up passport.js (Secure SPA Part 1)

Posted: April 30th, 2013 | Author: | Filed under: Uncategorized | 3 Comments »

This is the first part of a series of blog posts that will walk step-by-step through the process of creating a secure Single Page App (SPA) using node.js, passport.js, and Angular.

Subsequent parts will cover:

  • Handling login, logout, and registration from a single page app
  • Adding in robust remember-me functionality
  • Protecting against CSRF
  • Protecting against XSS
  • Adding a reasonably robust remember-me to passport js
  • Enhancing security by adding some simple HTTP headers
  • Setting up to always redirect to SSL
  • Pushing our app to Heroku
  • Throttling login attempts

To get started, though, let’s just get Passport up and running using a mongoose backend. This starting point is based heavily off the multiple file example provided with passport-local, so shout out to andr3w321 for providing that example!

Prerequisites

You’ll need Node.js of course! Then make sure you’ve got MongoDB and finally Mongoose. You only need to install Mongoose, but you do need to start mongod – see the MongoDB docs for instructions.

Let’s get started

First off, grab the code:

git clone https://github.com/studds/secure-spa-part-1.git

Change in to the secure-spa-part-1 directory and run

npm install
grunt dbseed

What just happened?

Those commands:

  1. Downloaded the dependencies
  2. Installed jake (javascript make) globally, so we can use it on the command line
  3. Seeded our database (called “secure-spa-part-1″ by default) with some test users.

Now run:

node app.js

… and take a look at http://localhost:3000/
Passport-Local Example-Logged-In

The login page:
Passport-Local Example-Login 

After logged in:
Passport-Local Example-Index
I’ll let you explore the rest of that yourself.

The code

Setting up the database – db/schema.js

// Database connect
var uristring = 
  process.env.MONGOLAB_URI || 
  process.env.MONGOHQ_URL || 
  'mongodb://localhost/secure-spa-part-1';

var mongoOptions = { db: { safe: true }};

mongoose.connect(uristring, mongoOptions, function (err, res) {
  if (err) { 
    console.log ('ERROR connecting to: ' + uristring + '. ' + err);
  } else {
    console.log ('Successfully connected to: ' + uristring);
  }
});

First, we work out the URI to connect to the Mongo DB. The first two options (process.env.MONGOLAB_URI and process.env.MONGOHQ_URL) are included so that (later on) we can push this straight to Heroku 1.

Next, we set that mongo should run in safe mode. This means that we’ll wait for our inserts to succeed before going on. This is important when we’re altering passwords, but comes at a performance cost which might not be right for all apps. This is something we’ll come back to 2.

Then, we connect to the database. Easy.

I’m going to skip over setting up the mongoose schema – there are good docs and tutorials elsewhere that will explain that. Let’s just look at what we’re adding for security:

// Bcrypt middleware
userSchema.pre('save', function(next) {
	var user = this;

	if(!user.isModified('password')) return next();

	bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
		if(err) return next(err);

		bcrypt.hash(user.password, salt, function(err, hash) {
			if(err) return next(err);
			user.password = hash;
			next();
		});
	});
});

// Password verification
userSchema.methods.comparePassword = function(candidatePassword, cb) {
	bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
		if(err) return cb(err);
		cb(null, isMatch);
	});
};

The ‘pre’ function will run before we save the model to the database. We need to do it this way because the hash function is asynchronous, and we need to make sure the clear text password is never saved to the database. When comparing the passwords, bcrypt has helpfully embedded the sale into the hash itself, so we don’t need to store that ourselves. For more details on this code, see 3.

Passport setup – config/pass.js

First up, we need to tell passport how it can serialize and deserialize users. The serialise function is called when the user logs in. Whatever it passes as the second parameter to done will be stored in the session 4 Deserialize is called is does just the opposite: it takes the id stored in the session and we use that id to retrieve our user.

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

passport.deserializeUser(function(id, done) {
  db.userModel.findById(id, function (err, user) {
    done(err, user);
  });
});

Next up, we configure passport to use LocalStrategy – this is the middleware that will actually do the authentication. Passport extracts the username and password from the body, and passes them to this function, which then looks the user up in the database, and checks that the passwords match.

passport.use(new LocalStrategy(function(username, password, done) {
  db.userModel.findOne({ username: username }, function(err, user) {
    if (err) { return done(err); }
    if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
    user.comparePassword(password, function(err, isMatch) {
      if (err) return done(err);
      if(isMatch) {
        return done(null, user);
      } else {
        return done(null, false, { message: 'Invalid password' });
      }
    });
  });
}));

Finally, two simple route middlewares to check that the user is authenticated:

// Simple route middleware to ensure user is authenticated.  Otherwise send to login page.
exports.ensureAuthenticated = function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) { return next(); }
  res.redirect('/login')
};

// Check for admin middleware, this is unrelated to passport.js
// You can delete this if you use different method to check for admins or don't need admins
exports.ensureAdmin = function ensureAdmin(req, res, next) {
        if(req.user && req.user.admin === true)
            next();
        else
            res.send(403);
};

And now for something completely different – app.js

After all that build up, we come to app.js. We’re going to do a few things here that will lay the foundation of our SPA. Most notably, we’re going to establish a few base URLs for different security levels. This will come in handy when we’re creating the REST API that sits behind our app.

/
Everything, including public files – this is unsecured
/public
An explicit directory for public files – this is unsecured
/secure
Secure directory – must be logged in to access this
/secure/admin
Admin directory – must be logged in AND an admin to access this
// use express.session before passport, so that passport session will work
app.use(express.session({ secret: 'keyboard cat' }));
// Initialize Passport!  Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
// clearly denote public content
app.use('/public', express.static('public'));

// set up our security to be enforced on all requests to secure paths
app.all('/secure', pass.ensureAuthenticated);
app.all('/secure/admin', pass.ensureAdmin);

// Basic pages
app.get('/', basic_routes.index);

// Login pages
app.get('/dmz/login', user_routes.getlogin);
app.post('/dmz/login', user_routes.postlogin);
app.get('/dmz/logout', user_routes.logout);

// secure pages
app.get('/secure/account', user_routes.account);

//admin pages
app.get('/secure/admin', user_routes.admin);

Well, that’s our foundation in place. Stay tuned for part 2, where we’ll add in a registration form.


Sign up for danielstudds.com
* = required field

3 Comments on “Setting up passport.js (Secure SPA Part 1)”

  1. 1 Daniel Studds » Blog Archive » Adding a registration form (Secure SPA Part 2) said at 1:31 am on May 3rd, 2013:

    [...] you’re new here, you should start at Part 1, where we set up Passport.js. Today we’re going to add in a signup page. We’ll be enforcing a minimum password [...]

  2. 2 Daniel Studds » Blog Archive » Basic Angular front-end for authentication (SPA Part 3) said at 10:27 pm on May 7th, 2013:

    [...] you’re new here, you should start at Part 1, where we set up Passport.js. Today, we’re going to get started with Angular. We’re just going to ease in to it: [...]

  3. 3 bema said at 3:20 am on January 5th, 2014:

    This was pretty useful. Thanks


Leave a Reply