NodeJS Express Passport

Sign in

Sign in system is one of a required feature in web service.
This is common feature, but it’s not easy to implement.
Passport is middleware of NodeJS Express.
It’s so useful.
Details are git hub

Install

npm install passport

Good Examples

The fastest way to learn passport is reading sample codes.
passport-local provides good for it. git hub
This implementation includes mongodb(mongoose).

passport-local
|- examples
    |- express3-mongoose
    |- login
    |- express3-multiple-files
    |- express3-mongoose-rememberme

Minimum dependencies

  • Passport
  • Connect-Flash
  • Passport Local
var flash = require('connect-flash');
var passport = require('passport')
var util = require('util')
var LocalStrategy = require('passport-local').Strategy;

Middleware

app.configure(function() {
  app.use(express.cookieParser());			// Add
  app.use(express.bodyParser());
  app.use(express.session({ secret: 'yoursecret' }));	// Add
  app.use(passport.initialize());			// Add
  app.use(passport.session());				// Add
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

“Add” is additional middleware to use Passport.


Session

user must be serialized to the session, also deserialized
Using user ID serialization

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

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

// deserialize from user object

Authenticate Request

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

success : Go /
Fail : Go /login

Other example

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
  function(req, res) {
    res.redirect('/');
});

Strategy

Authentication strategy

  • LocalStrategy
  • OAuth
  • OpenID

Other strategy is here


logout

It’s so simple.
Only call req.logout

app.get('/logout', function(req, res){
  req.logout();
  res.redirect('/');
});

Verification method(middleware)

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

Sample

This is same as express3-mongoose.

package.json

Add dependencies

"dependencies": {
    "express": "3.2.6",
    "ejs": "*",
    "connect-flash": "*",
    "ejs-locals": "*",
    "passport": "*",
    "passport-local": "*",
    "mongodb": "*",
    "mongoose": "*",
    "bcrypt": "*"
  }
npm install

app.js

All related codes in this js file.

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path')
  , passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy
  , mongodb = require('mongodb')
  , mongoose = require('mongoose')
  , bcrypt = require('bcrypt');

var SALT_WORK_FACTOR = 10;

// mongodb connection check
mongoose.connect('localhost', 'test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback(){
  console.log('Connected to DB');
});

// User Schema
var userSchema = mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true},
});

//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);
  });
};

//Seed a user
var User = mongoose.model('User', userSchema);
var user = new User({ username: 'yoona', email: 'yoona@snsd.com', password: 'secret' });
user.save(function(err) {
  if(err) {
    console.log(err);
  } else {
    console.log('user: ' + user.username + " saved.");
  }
});

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

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

passport.use(new LocalStrategy(function(username, password, done) {
  User.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' });
      }
    });
  });
}));

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs-locals'));
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());    //
app.use(express.bodyParser());
app.use(express.methodOverride());

app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());

app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', function(req, res){
  res.render('index', { user: req.user });
});

app.get('/account', ensureAuthenticated, function(req, res){
  res.render('account', { user: req.user });
});

app.get('/login', function(req, res){
  res.render('login', { user: req.user, message: req.session.messages });
});

app.post('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err) }
    if (!user) {
      req.session.messages =  [info.message];
      return res.redirect('/login')
    }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/');
    });
  })(req, res, next);
});

app.get('/logout', function(req, res){
  req.logout();
  res.redirect('/');
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

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

index.ejs

Top page layout

<% layout('layout') -%>
<% if (!user) { %>
	<h2>Welcome! Please log in.</h2>
<% } else { %>
	<h2>Hello, <%= user.username %>.</h2>
<% } %>

layout.ejs

Common layout

<!DOCTYPE html>
<html>
	<head>
		<title>Passport-Local Example</title>
	</head>
	<body>
		<% if (!user) { %>
			<p>
			<a href="/">Home</a> | 
			<a href="/login">Log In</a>
			</p>
		<% } else { %>
			<p>
			<a href="/">Home</a> | 
			<a href="/account">Account</a> | 
			<a href="/logout">Log Out</a>
			</p>
		<% } %>
		<%- body %>
	</body>
</html>

login.ejs

sign in page

<% layout('layout') -%>
<% if (message) { %>
<p><%= message %></p>
<% } %>
<form action="/login" method="post">
	<div>
	<label>Username:</label>
	<input type="text" name="username"/><br/>
	</div>
	<div>
	<label>Password:</label>
	<input type="password" name="password"/>
	</div>
	<div>
	<input type="submit" value="Submit"/>
	</div>
</form>
<p><small>Hint - bob:secret</small></p>

account.ejs

User info confirmation page

<% layout('layout') -%>
<p>Username: <%= user.username %></p>
<p>Email: <%= user.email %></p>