saintedlama / passport-local-mongoose

Passport-Local Mongoose is a Mongoose plugin that simplifies building username and password login with Passport
MIT License
1.17k stars 295 forks source link
javascript mongoose-plugin passport

Passport-Local Mongoose

Passport-Local Mongoose is a Mongoose plugin that simplifies building username and password login with Passport.

Node.js CI Coverage Status

Tutorials

Michael Herman gives a comprehensible walk through for setting up mongoose, passport, passport-local and passport-local-mongoose for user authentication in his blog post User Authentication With Passport.js

Installation

> npm install passport-local-mongoose

Passport-Local Mongoose does not require passport or mongoose dependencies directly but expects you to have these dependencies installed.

In case you need to install the whole set of dependencies

> npm install passport mongoose passport-local-mongoose

Usage

Plugin Passport-Local Mongoose

First you need to plugin Passport-Local Mongoose into your User schema

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportLocalMongoose = require('passport-local-mongoose');

const User = new Schema({});

User.plugin(passportLocalMongoose);

module.exports = mongoose.model('User', User);

You're free to define your User how you like. Passport-Local Mongoose will add a username, hash and salt field to store the username, the hashed password and the salt value.

Additionally, Passport-Local Mongoose adds some methods to your Schema. See the API Documentation section for more details.

Configure Passport/Passport-Local

You should configure Passport/Passport-Local as described in the Passport Guide.

Passport-Local Mongoose supports this setup by implementing a LocalStrategy and serializeUser/deserializeUser functions.

To setup Passport-Local Mongoose use this code

// requires the model with Passport-Local Mongoose plugged in
const User = require('./models/user');

// use static authenticate method of model in LocalStrategy
passport.use(new LocalStrategy(User.authenticate()));

// use static serialize and deserialize of model for passport session support
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Make sure that you have mongoose connected to mongodb and you're done.

Simplified Passport/Passport-Local Configuration

Starting from version 0.2.1, passport-local-mongoose adds a helper method createStrategy as static method to your schema. The createStrategy is responsible to setup passport-local LocalStrategy with the correct options.

const User = require('./models/user');

// CHANGE: USE "createStrategy" INSTEAD OF "authenticate"
passport.use(User.createStrategy());

passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

The reason for this functionality is that when using the usernameField option to specify an alternative usernameField name, for example "email" passport-local would still expect your frontend login form to contain an input field with name "username" instead of email. This can be configured for passport-local but this is double the work. So we got this shortcut implemented.

Async/Await

Starting from version 5.0.0, passport-local-mongoose is async/await enabled by returning Promises for all instance and static methods except serializeUser and deserializeUser.

const user = new DefaultUser({username: 'user'});
await user.setPassword('password');
await user.save();
const { user } = await DefaultUser.authenticate()('user', 'password');

Options

When plugging in Passport-Local Mongoose plugin, additional options can be provided to configure the hashing algorithm.

User.plugin(passportLocalMongoose, options);

Main Options

Attention! Changing any of the hashing options (saltlen, iterations or keylen) in a production environment will prevent existing users from authenticating!

Error Messages

Override default error messages by setting options.errorMessages.

Hash Algorithm

Passport-Local Mongoose use the pbkdf2 algorithm of the node crypto library. Pbkdf2 was chosen because platform independent (in contrary to bcrypt). For every user a generated salt value is saved to make rainbow table attacks even harder.

Examples

For a complete example implementing a registration, login and logout see the login example.

API Documentation

Instance methods

setPassword(password, [cb])

Sets a user password. Does not save the user object. If no callback cb is provided a Promise is returned.

changePassword(oldPassword, newPassword, [cb])

Changes a user's password hash and salt, resets the user's number of failed password attempts and saves the user object (everything only if oldPassword is correct). If no callback cb is provided a Promise is returned. If oldPassword does not match the user's old password, an IncorrectPasswordError is passed to cb or the Promise is rejected.

authenticate(password, [cb])

Authenticates a user object. If no callback cb is provided a Promise is returned.

resetAttempts([cb])

Resets a user's number of failed password attempts and saves the user object. If no callback cb is provided a Promise is returned. This method is only defined if options.limitAttempts is true.

Callback Arguments

Using setPassword() will only update the document's password fields, but will not save the document. To commit the changed document, remember to use Mongoose's document.save() after using setPassword().

Error Handling

All those errors inherit from AuthenticationError, if you need a more general error class for checking.

Static methods

Static methods are exposed on the model constructor. For example to use createStrategy function use

const User = require('./models/user');
User.createStrategy();

Examples

Allow only "active" users to authenticate

First, we define a schema with an additional field active of type Boolean.

const UserSchema = new Schema({
  active: Boolean
});

When plugging in Passport-Local Mongoose, we set usernameUnique to avoid creating a unique mongodb index on field username. To avoid non active users being queried by mongodb, we can specify the option findByUsername that allows us to restrict a query. In our case we want to restrict the query to only query users with field active set to true. The findByUsername MUST return a Mongoose query.

UserSchema.plugin(passportLocalMongoose, {
  // Set usernameUnique to false to avoid a mongodb index on the username column!
  usernameUnique: false,

  findByUsername: function(model, queryParameters) {
    // Add additional query parameter - AND condition - active: true
    queryParameters.active = true;
    return model.findOne(queryParameters);
  }
});

To test the implementation, we can simply create (register) a user with field active set to false and try to authenticate this user in a second step:

const User = mongoose.model('Users', UserSchema);

User.register({username:'username', active: false}, 'password', function(err, user) {
  if (err) { ... }

  const authenticate = User.authenticate();
  authenticate('username', 'password', function(err, result) {
    if (err) { ... }

    // Value 'result' is set to false. The user could not be authenticated since the user is not active
  });
});

Updating from 1.x to 2.x

The default digest algorithm was changed due to security implications from sha1 to sha256. If you decide to upgrade a production system from 1.x to 2.x, your users will not be able to login since the digest algorithm was changed! In these cases plan some migration strategy and/or use the sha1 option for the digest algorithm.

License

Passport-Local Mongoose is licensed under the MIT license.