How to Passport-local + Railway-passport – Nodejs

avril 11th, 2012

Here is how to set up a common and traditional way, with Railwayjs and Passportjs, for websites to authenticate users via a site-specific user email protected by a password.

Passport is an authentication middleware for Node.js. It use a comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.

Passportjs auth library integrated into Railwayjs need a specific configuration, that we’ll describe in this post.

In order to set up the complete process, we need to install passport, Compound.js and passport-local middleware in our Railway project.

Passport middleware

Passport is an authentication framework for Node. It is designed to serve a singular purpose : authenticate requests. Passport delegates all other functionalities to the application. Traditionally, users log in by providing a username and password. With the rise of social networking, single sign-on using an OAuth provider such as Facebook or Twitter has become a popular authentication method.

Passport recognizes that each application has unique authentication requirements. Authentication mechanisms, known as strategies, are packaged as individual modules (passport-local, passport-facebook, passport-twitter,…). Applications can choose which strategies to employ, without creating unnecessary dependencies.

First of all, we need to to install passport into our railway based project. package.json file need to be updated with the following line :


"oauth":    ">= 0", // used by passport
"yaml":     ">= 0", // used by railway-passport for config purpose
"passport": ">= 0", // passport itself

Here’s the full package.json file :


{ "name": "TestRailwayPassport"
, "version": "0.0.1"
, "engines": ["node >= 0.4.0"]
, "main": "server.js"
, "dependencies":
  { "ejs":       ">= 0"
  , "ejs-ext":   ">= 0"
  , "express":          ">= 2.2.2"
  , "connect":          ">= 1.4.2"
  , "railway":          ">= 0.1.6"
  , "coffee-script":    ">= 1.1.1"
  , "mime":             ">= 1.2.2"
  , "qs":               ">= 0.1.0"
  , "nodeunit":         ">= 0"
  , "sinon":            ">= 0"
  , "mongoose":         ">= 0"
  , "oauth":            ">= 0"  
  , "yaml":             ">= 0"
  , "passport":         ">= 0"
  }
, "scripts":
  { "test": "nodeunit test/*/*"
  }
}

Then run :

npm -l install

This will install passport, yaml and oauth middlewares into the project.

Install passport-local strategy and Railway-passport

Railway-passport handle all passport’s strategies and passport-local is the middleware used for username and password authentication.

The command lines to install are :


npm -l install passport-local
npm -l install railway-passport

You should now have these packages in your node_modules directory :

  • oauth
  • passport
  • passport-local
  • railway-passport
  • yaml

Railway-passport config

Railway-passport need to be declare to be loaded during startup. In npmfile.js, add the following line :

require('railway-passport');

Every strategies should have it’s own configuration in an yml file called passport.yml. The content of this file in config/passport.yml is the following :


development:
  baseURL
: 'http://localhost:3000/'
  redirURL
: '/home'
  local
: yes
production
:
  baseURL
: 'http://physalix.com'
  redirURL
: '/home'
  local
: yes

The local strategy is declare as: local: yes.

The Strategy implements support for this mechanism is located in the local.js file in railway-passport/lib/strategies directory. Here’s the full local.js file:


var passport = require('passport');

exports.callback = function(email, password, done) {
    User.findOrCreate({
        email: email,
        password: password
    }, function (err, user) {
        if (err) { return done(err); }
        if (!user) { return done(err, false); }
        if (!User.verifyPassword(password, user.password)) { return done(err, false); }
  return done(err, user);
    });
};

exports.init = function (conf) { 
    var Strategy = require('passport-local').Strategy;

    passport.use(new Strategy({
        callbackURL: conf.baseURL + 'auth/login/callback'
    }, exports.callback));

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

    app.get('/auth/login/callback',
        passport.authenticate('local', { failureRedirect: '/' }),
        exports.redirectOnSuccess);

};

Here’s the full user.js file and all strategies tested :


exports.findOrCreate = function (data, done) {

    /* GITHUB */
    if (data.githubId) {
        User.all({
            where: {
                githubId: data.githubId
            }, limit: 1
        }, function (err, user) {
            if (user[0]) return done(err, user[0]);
            User.create({
                githubId: data.githubId,
                username: data.profile.displayName || data.profile.username
            }, done);
        });
    } else

    /* GOOGLE OPENID */
    if (data.openId) {
        User.all({
            where: {
                googleId: data.openId
            }, limit: 1
        }, function (err, user) {
            if (user[0]) return done(err, user[0]);
            User.create({
                username: data.profile.displayName,
                email: data.profile.emails[0].value,
                googleId: data.openId
            }, done);
        });
    } else

    /* LINKEDIN */
    if (data.linkedinId) {
        User.all({
            where: {
                linkedinId: data.linkedinId
            }, limit: 1
        }, function (err, user) {
            if (user[0]) return done(err, user[0]);
            User.create({
                username: data.profile.displayName,
                linkedinId: data.linkedinId
            }, done);
        });
    } else

    /* LOCAL */
    if (data.email) {
        User.all({
            where: {
               email: data.email
            }, limit: 1
        }, function (err, user) {          
            if (user[0]) return done(err, user[0]);
      if (!user[0]) return done(err);
        });

    } else

    /* SOMETHING NOT KNOWN YET */
    {
        console.log(data.profile);
    }
};

exports.verifyPassword = function (password, user_password) {
    if(!password && !user_password){
  return false;
    }
    if(password == user_password){
  return true;
    }
    return false;
}

Change username to email in Passport-local Strategy

The most user-friendly solution, to sign in, is to use the minimal (yet still secure enough) number of data into the service. So, basically, using email address to sign in is the better idea. This is also not giving you much additional help with limiting data within database, because you should not display the email address as the username (thus you should not get rid of username field in the database if you still need to identify the user to different users).

To sign in with email, you must change a peace of code in passport-local/lib/passport-local/strategy.js. Here is the code modified :


...

function Strategy(options, verify) {
  if (typeof options == 'function') {
    verify = options;
    options = {};
  }
  if (!verify) throw new Error('local authentication strategy requires a verify function');
 
  this._usernameField = options.usernameField || 'email';
  this._passwordField = options.passwordField || 'password';

  passport.Strategy.call(this);
  this.name = 'local';
  this._verify = verify;
}

...

And finally the form to sign in :


<form accept-charset="utf-8" method="post" action="http://localhost:3000/auth/login">
   Email: <input type="text" name="email" value="" id="email" maxlength="128" data-ime-mode-disabled="">
   Password: <input type="password" name="password" value="" id="password" maxlength="128" data-ime-mode-disabled="">                  
   <input type="submit" value="Signin">
</form>

Enjoy !