This package has been deprecated

Author message:

DO NOT USE THIS PACKAGE. It was never production ready and now is no longer safe. Both dependencies and defaults are deprecated and NOT SAFE.

passport-local-mongoose-argon

1.0.1 • Public • Published

Passport Local Mongoose Argon

Passport Local Mongoose Argon is a Mongoose plugin, a fork of the original Passport-Local-Mongoose that simplifies building username and password logins with Passport.

This fork uses Argon2 in this Node.js binding for extra security when hashing and storing passwords.

This is a work in progress!

Acknowledgements

This module is very heavily based on the work of Christoph Walcher for the original Passport Local Mongoose plugin and Ranieri Althoff for the Argon2 Node binding. All credit goes to them and their collaborators.

BEFORE Installation

The Argon2 implementation also requires a global instalation of node-gyp and GCC.

The easiest way to do this on Windows is to npm install --global --production windows-build-tools and then npm install -g node-gyp;

On MacOS and Unix you'll need to follow these instructions.

Installation

AFTER you've installed the dependencies listed above you can go ahead and simply npm install --save passport-local-mongoose-argon and then require it in your app.

Compatibility

This package was tested against projects built with passport-local-mongoose @ 4.x.x. It should work as a drop-in replacement in almost any project on a recent version.

It WILL NOT work when updating from older versions due to breaking changes.

It WILL NOT work with hashes from a non-Argon2 algorithm. It is NOT backward compatible with the PBKDF2 versions.

This SHOULD NOT be used in production until further testing. If you find bugs, especially security-related, raise an issue on this repo.

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

For more details on the original passport-local-mongoose you can check out its repo. Most of its excellent functionality has been preserved and some of the examples are reproduced below.

Usage

Plugin Passport Local Mongoose Argon

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

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportLocalMongooseArgon = require('passport-local-mongoose-argon');
 
const User = new Schema({});
 
User.plugin(passportLocalMongooseArgon);
 
module.exports = mongoose.model('User', User);

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

Additionally Passport Local Mongoose Argon 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 Argon supports this setup by implementing a LocalStrategy and serializeUser/deserializeUser functions.

To setup Passport Local Mongoose Argon use this code

const User = require('./models/user');
 
//USE "createStrategy" INSTEAD OF "authenticate" in older versions of the original
passport.use(User.createStrategy());
 
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());

Options

When plugging in Passport Local Mongoose Argon additional options can be provided to configure the hashing algorithm as well as the plugin itself.

User.plugin(passportLocalMongoose, options);

Argon options map directly to the Argon2 hashing options, and can even be changed in production. Older Argon2 hashes will still work. However, the defaults listed below should be good enough for all cases.

Argon Options

  • timeCost: specifies the number of passes over memory; hashing time goes up linearly with this option
  • memoryCost: specifies the amount of memory each call can afford; expressed in 2^(X) kibibytes. Ex: memoryCost: 12 is equal to 4,096 KiB/ call
  • parallelism: specifies the number of threads available to each call.
  • type: 0, 1, 2 for Argon2d, Argon2i and Argon2id respectively
  • hashLength: specifies the hashLength in bytes

Argon Defaults are set in accordance with the Argon2 RFC and are in theory safe for every use case. You don't need to change them unless you're going for something very specific. If you find hashing takes too long even with timeCost set to 1, gradually lower memoryCost and parallelism.

Argon Defaults

  • timeCost: 1,
  • memoryCost: 12,
  • parallelism: 8,
  • type: 2 // meaning Argon2id
  • hashLength: 32 // meaning 256 bits

On top of the Argon options you also have a large number of configuration options kept from the original plugin, to enhance your db models and authentication security.

Plugin Options

  • interval: specifies the interval in milliseconds between login attempts. Default: 100
  • usernameField: specifies the field name that holds the username. Defaults to 'username'. This option can be used if you want to use a different field to hold the username for example "email".
  • usernameUnique : specifies if the username field should be enforced to be unique by a mongodb index or not. Defaults to true.
  • hashField: specifies the field name that holds the password hash value. Defaults to 'hash'.
  • attemptsField: specifies the field name that holds the number of login failures since the last successful login. Defaults to 'attempts'.
  • lastLoginField: specifies the field name that holds the timestamp of the last login attempt. Defaults to 'last'.
  • selectFields: specifies the fields of the model to be selected from mongodb (and stored in the session). Defaults to 'undefined' so that all fields of the model are selected.
  • usernameLowerCase: convert username field value to lower case when saving an querying. Defaults to 'false'.
  • populateFields: specifies fields to populate in findByUsername function. Defaults to 'undefined'.
  • limitAttempts: specifies whether login attempts should be limited and login failures should be penalized. Default: false.
  • maxAttempts: specifies the maximum number of failed attempts allowed before preventing login. Default: Infinity.
  • passwordValidator: specifies your custom validation function for the password in the form 'function(password,cb)'. Default: validates non-empty passwords.
  • usernameQueryFields: specifies alternative fields of the model for identifying a user (e.g. email).
  • findByUsername: Specifies a query function that is executed with query parameters to restrict the query with extra query parameters. For example query only users with field "active" set to true. Default: function(model, queryParameters) { return model.findOne(queryParameters); }. See the examples section for a use case.

Where's the salt?

The Argon2 Node binding generates a 128-bit salt using crypto.randomBytes for each password in compliance with the RFC. The salt, and the hashing options get stored directly in the hash. This lets you change current hashing options as time goes on while still being able to verify previously stored hashes with different options.

If you want even more security you can add your own pepper manually to the user's password before calling the register / change password methods.

Error Messages

Override default error messages by setting options.errorMessages.

  • MissingPasswordError 'No password was given'
  • AttemptTooSoonError 'Account is currently locked. Try again later'
  • TooManyAttemptsError 'Account locked due to too many failed login attempts'
  • IncorrectPasswordError 'Password or username are incorrect'
  • IncorrectUsernameError 'Password or username are incorrect'
  • MissingUsernameError 'No username was given'
  • UserExistsError 'A user with the given username is already registered'

Examples

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

API Documentation

Instance methods

setPassword(password, cb)

asynchronous method to set a user's password hash

changePassword(oldPassword, newPassword, cb)

asynchronous method to change a user's password hash. If oldPassword does not match the user's old password an IncorrectPasswordError is passed to cb.

authenticate(password, cb)

asynchronous method to authenticate a user instance

resetAttempts(cb)

asynchronous method to reset a user's number of failed password attempts (only defined if options.limitAttempts is true)

callback arguments

  • err
    • null unless the hasing algorithm throws an error
  • thisModel
    • the model getting authenticated if authentication was successful otherwise false
  • passwordErr
    • an instance of AuthenticationError describing the reason the password failed, else undefined.

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

  • IncorrectPasswordError: specifies the error message returned when the password is incorrect. Defaults to 'Incorrect password'.
  • IncorrectUsernameError: specifies the error message returned when the username is incorrect. Defaults to 'Incorrect username'.
  • MissingUsernameError: specifies the error message returned when the username has not been set during registration. Defaults to 'Field %s is not set'.
  • MissingPasswordError: specifies the error message returned when the password has not been set during registration. Defaults to 'Password argument not set!'.
  • UserExistsError: specifies the error message returned when the user already exists during registration. Defaults to 'User already exists with name %s'.
  • AttemptTooSoonError: Occurs if the option limitAttempts is set to true and a login attept occures while the user is still penalized.
  • TooManyAttemptsError: Returned when the user's account is locked due to too many failed login attempts.

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();
  • authenticate() Generates a function that is used in Passport's LocalStrategy
  • serializeUser() Generates a function that is used by Passport to serialize users into the session
  • deserializeUser() Generates a function that is used by Passport to deserialize users into the session
  • register(user, password, cb) Convenience method to register a new user instance with a given password. Checks if username is unique. See login example.
  • findByUsername() Convenience method to find a user instance by it's unique username.
  • createStrategy() Creates a configured passport-local LocalStrategy instance that can be used in passport.

Examples

Allow only "active" users to authenticate

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

var 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 to be 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, {
  // Needed to set usernameUnique to true 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:

var User = mongoose.model('Users', UserSchema);
 
User.register({username:'username', active: false}, 'password', function(err, user) {
  if (err) { ... }
 
  var 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
    
  });
});

License

Passport Local Mongoose Argon is licensed under the MIT license.

Package Sidebar

Install

npm i passport-local-mongoose-argon

Weekly Downloads

0

Version

1.0.1

License

MIT

Last publish

Collaborators

  • avladd