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>