In this section we will be integrating passport authentication.
Passport is simple, unobtrusive authentication for Node.js
With Passport Authentication strategies are broken into separate modules. There are modules for all of the popular third party providers; Facebook, twitter, Google, etc. In this section we will be incorporating the local strategy. This is a strategy that takes a username
and password
and returns an authenticated user. We will be replacing the username
with email
.
For more information on the passport-local strategy please see.
To begin add Passport and passport-local to your package.json
file
package.json
"passport": "0.1.x",
"passport-local": "0.1.x" ,
Updated your dependencies
npm install
Lets write some test.
test/auth/routes.js
'use strict';
// import the moongoose helper utilities
var utils = require('../utils');
// import our express app. We will be passing this to supertest
var app = require('../../app').app;
var should = require('should');
var request = require('supertest');
var User = require('../../users/models').User;
describe('Passport: routes', function () {
var baseUrl = '/auth/local';
var emailAddress = 'berry@example.com';
var realPassword = 'secret1';
// This function will run before each test.
beforeEach(function (done) {
// TODO this should be refactored into a User.new() function.
// Hash the password
User.hashPassword(realPassword, function (err, passwordHash) {
// Create a User
var u = {
passwordHash: passwordHash,
emails: [
{
value: emailAddress
}
]
};
User.create(u, function (err, u) {
// call the done() method so the mocha knows we are done.
done();
});
});
});
describe('POST /auth/local', function () {
it('should redirect to "/account" if authentication fails', function (done) {
// post is what we will be sending to the /auth/local
var post = {
email: 'berry@example.com',
password: realPassword
};
request(app)
.post(baseUrl)
.send(post)
.expect(302)
.end(function (err, res) {
should.not.exist(err);
// confirm the redirect
res.header.location.should.include('/account');
done();
});
});
it('should redirect to "/login" if authentication fails', function (done) {
var post = {
email: 'berry@example.com',
password: 'fakepassword'
};
request(app)
.post(baseUrl)
.send(post)
.expect(302)
.end(function (err, res) {
should.not.exist(err);
// confirm the redirect
res.header.location.should.include('/login');
done();
});
});
});
});
Our tests are now failing.
✖ 2 of 11 tests failed:
1) Passport: routes POST /auth/local should redirect to "/account" if authentication fails:
AssertionError: expected [Error: expected 302 "Moved Temporarily", got 404 "Not Found"] to not exist
2) Passport: routes POST /auth/local should redirect to "/login" if authentication fails:
AssertionError: expected [Error: expected 302 "Moved Temporarily", got 404 "Not Found"] to not exist
Add Passport support to app.js
and the POST
/auth/local
route
app.js
//... previous imports
var auth = require('./auth/routes');
var passport = require('passport');
var app = exports.app = express();
app.configure(function () {
//... previous config
// Add passport support
// Important: This must come before the app.router middleware
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});
//... previous routes
app.post('/auth/local', auth.local);
//... previous code
Create the route handler:
We are doing quite a bit here. First we are telling Passport how to serialize and deserialize our user. Passport will create a cookie to store the user.id
this is convenient when you are using more then one strategy
In the next section we are telling Passport about our local strategies, we are changing the usernameField
from 'username'
to 'email'
, and we are looking up the user by the email. If one is not found we return 'Unknown email address ...'. If one is found we verify that the provided password matches. Note the function that we are returning. This is the same function that will be called in the next section.
In the final section we are overriding the authentication function. If you notice the passed function has the same argument as the function that we returned in the previous section. Here we have an opportunity to return something to the user. If thing succeeds successfully we redirect the user to '/account' if there is an error. We redirect them back to '/login'.
That's it. All of these are standard Passport settings
auth/routes.js
'use strict';
// auth/local provides routes for POST authentication.
var passport = require('passport');
var User = require('../users/models').User;
var LocalStrategy = require('passport-local').Strategy;
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function (user, fn) {
fn(null, user.id);
});
// deserializeUser is passed a function that will return the user the
// belongs to an id.
passport.deserializeUser(function (id, fn) {
User.findOne({_id: id}, function (err, user) {
fn(err, user);
});
});
// Use the LocalStrategy within Passport.
// Strategies in passport require a `verify` function, which accept
// credentials (in this case, a username and password), and invoke a callback
// with a user object. In the real world, this would query a database;
// however, in this example we are using a baked-in set of users.
passport.use(new LocalStrategy(
// We are using email for verification so we set the name of
// usernameField to 'email'. This allows use to post: email and
// password
{ usernameField: 'email' },
// This is the callback function it will be passed the email and
// password that have been submitted.
function (email, password, fn) {
// We need to look up the user by email address
User.findOne({'emails.value': email}, function (err, usr) {
if (err) {
return fn(err, false, { message: 'An Error occurred' });
}
// no user then an account was not found for that email address
if (!usr) {
return fn(err, false, { message: 'Unknown email address ' + email });
}
// If we have a user lets compare the provided password with the
// user's passwordHash
User.comparePasswordAndHash(password, usr.passwordHash, function (err, valid) {
if (err) {
return fn(err);
}
// if the password is invalid return that 'Invalid Password' to
// the user
if (!valid) {
return fn(null, false, { message: 'Invalid Password' });
}
return fn(err, usr);
});
});
}
));
// POST */auth/local
exports.local = function (req, res, fn) {
passport.authenticate('local', function (err, user, info) {
if (err) {
return fn(err);
}
if (!user) {
return res.redirect('/login');
}
req.logIn(user, function (err) {
if (err) {
return fn(err);
}
return res.redirect('/account');
});
})(req, res, fn);
};
Now if we check our test runner we will see that all of our test are passing:
addition
✓ should add 1+1 correctly
◦ should return 2 given the url /add/1/1: GET /add/1/1 200 3ms - 1
✓ should return 2 given the url /add/1/1
Passport: routes
POST /auth/local
◦ should redirect to "/account" if authentication fails: POST /auth/local 302 7ms - 58
✓ should redirect to "/account" if authentication fails
◦ should redirect to "/login" if authentication fails: POST /auth/local 302 14ms - 56
✓ should redirect to "/login" if authentication fails
Users: models
#create()
✓ should create a new User
#hashPassoword()
✓ should return a hashed password asynchronously
#comparePasswordAndHash()
✓ should return true if password is valid
✓ should return false if password is invalid
Users: routes
POST /signup
◦ should redirect to "/account" if the form is valid: POST /signup 302 7ms - 58
✓ should redirect to "/account" if the form is valid
◦ should redirect to "/login" if the form is invalid: POST /signup 302 1ms - 57
✓ should redirect to "/login" if the form is invalid
◦ should create a new User if the form is valid: POST /signup 302 12ms - 58
✓ should create a new User if the form is valid
✔ 11 tests complete (356 ms)