Building an authentication system in Node.js can be challenging, especially for integrating OAuth2.0 and authenticated API-based endpoints. Passport.js is great for connecting services and for a base to integrate OAuth, but getting Passport.js set up for an existing Mongoose User model can be a little challenging. In this guide, you’ll see the fastest and easiest way to integrate Mongoose and Passport.js for local user authentication.

Adding Libraries & the User Schema

For this tutorial, we’re assuming you already have an Express.js project set up. If you don’t, there are countless great guides for getting started with Express on various websites. After you’re set up in Express, you’ll need to install a few libraries that we’re going to be using in this project. The following command should get everything set up and saved into your package.json file:

$ npm install mongoose password-hash passport passport-local connect-flash --save

To start our app off, we need to create a user schema with Mongoose. If you’re adding Passport.js into an existing project, you probably already have a Mongoose schema for users, but make sure to add the authenticate static method into this schema to make implementing Passport a little easier later on. For this tutorial, we’re going to use email addresses rather than usernames (because in the end, it’s one less thing to make our users remember)—you’re free to replace email with username to ask for usernames instead. Here’s the schema we’re using (note that we’re automatically hashing the users password):

var Mongoose = require('mongoose');
var Hash = require('password-hash');
var Schema = Mongoose.Schema;

var UserSchema = new Schema({
	email: { type: String },
	password: { type: String, set: function(newValue) {
		return Hash.isHashed(newValue) ? newValue : Hash.generate(newValue);
	} },

	// ... add any other properties you want to save with users ...
});

UserSchema.statics.authenticate = function(email, password, callback) {
	this.findOne({ email: email }, function(error, user) {
		if (user && Hash.verify(password, user.password)) {
			callback(null, user);
		} else if (user || !error) {
			// Email or password was invalid (no MongoDB error)
			error = new Error("Your email address or password is invalid. Please try again.");
			callback(error, null);
		} else {
			// Something bad happened with MongoDB. You shouldn't run into this often.
			callback(error, null);
		}
	});
};

Because we want to keep our code organized, we’re adding that new static function authenticate(email, password, callback) to this model for checking if credentials match a user in our table. We’re going to use a callback function with two parameters, error, user (and they’ll translate nicely to Passport in that order).

Configuring a Passport Strategy

Passport.js uses Strategies for serving different sources of users. For example, a Facebook strategy would let your users sign in with Facebook, and in our case, a local strategy will let us use our own database to store account info. We already have a Mongoose user schema set up from the last step, so we’re ready to write a strategy for Passport now. We’re also going to add a serializer and deserializer, which will tell Passport how to save the login credentials in the browser session and retrieve them later, and we’re adding the connect-flash module into Express so we can show error messages for invalid credentials.

// ... set up & connect mongoose here ...

var User = mongoose.model(UserSchema);

var passport = require('passport');
var PassportLocalStrategy = require('passport-local');

var authStrategy = new PassportLocalStrategy({
	usernameField: 'email',
	passwordField: 'password'
}, function(email, password, done) {
	User.authenticate(email, password, function(error, user){
		// You can write any kind of message you'd like.
		// The message will be displayed on the next page the user visits.
		// We're currently not displaying any success message for logging in.
		done(error, user, error ? { message: error.message } : null);
	});
});

var authSerializer = function(user, done) {
	done(null, user.id);
};

var authDeserializer = function(id, done) {
	User.findById(id, function(error, user) {
		done(error, user);
	});
};

passport.use(authStrategy);
passport.serializeUser(authSerializer);
passport.deserializeUser(authDeserializer);

// ... continue with Express.js app initialization ...
app.use(require('connect-flash')()); // see the next section
app.use(passport.initialize());

Authenticating Users

By integrating Passport.js, authenticating users is now extremely simple. First, we’re going to need a form that sends an HTTP-POST request to our login route with a email field and a password field—if they’re named incorrectly, the form will not work. You can take care of that form page on your own. We’re going to handle that form with this route:

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

Also notice that we’re using Flash. We added connect-flash into our project, an extension for displaying one-time messages and objects. With Flash, we’re able to get error info during the next request (presumably for the login form again), as an array (either empty, or an array filled with a string error message). You can also use Flash for other kinds of messages, like success messages, or for persisting any Javascript object. Once a single Flash message is retrieved by your app with the code below, it will be removed and not redisplayed. With Flash, everything will be as an array (sometimes empty) for multiple messages.

app.get('/login', function(req, res, next) {
	var errors = req.flash('error');

	// req.flash('success', 'You can add messages by including a second parameter with the function.');
	
	// ... respond to the request ...
});

Success!

Congratulations on building a new Strategy for authenticating your users in Passport. Stay tuned for the next article on how to implement OAuth2.0 into a Mongoose local stategy with Passport. If you have any questions about this article or find any issues, just let me know on Twitter—@hnryjms.

Up Next
Codeblocks for WordPress