Hashing Passwords in Mongoose Model

Mongoose is a wrapper for MongoDB used in a lot of NodeJS and ExpressJS projects. Mongoose makes it easy to create models for database objects including model hooks and functions. Many projects store user account data such as email and password combinations in the database, one important part of storing sensitive information like user passwords is properly securing them. This means that not even the server administrator should be able to access the raw password that the user provided. We handle secure information using Hashing algorithms to hash the passwords and then store the hash in the database.

With Mongoose it’s pretty easy to create a hook that is called whenever the model object is updated that then checks if it’s the password that has changed and automatically hashes the password for us before storing it in the database. This saves a lot of code in other parts of the project since we don’t have to manually hash the password when it’s first set or each time it’s updated. It also puts all the hashing calls into a single file making it easier to keep track of in case of module updates and things like that, so you only have to edit a single file instead of tracking down hashing calls through several functions or endpoints.

It’s important to use good and up to date hashing algorithms when hashing passwords. For good updated information OWASP Password Storage Cheatsheet is a good reference. At this time Argon2 is a good option that’s pretty easy to implement in NodeJS so we will use that. In order to use Argon2 you will have to install an Argon2 module like npm i argon2

To represent this idea let’s create a simple User model

Models/User.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Mongoose
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Hash Module
const argon2 = require('argon2');

// Simple Model with just Email/Password combination
const user = new Schema({
email: String,
password: String
});

// Hook to fire this function before model is saved
user.pre('save', function(next) {
// If the password field has not been modified
// we can skip the rest of the function
if (!this.isModified('password')) return next();

try {
// Hash the password, store in variable for now
const hash = await argon2.hash(this.password);

// Update the document password
// field with the new hashed value
this.password = hash;

// Now that the password has been hashed
// and the field has been updated we can
// finish the function
next();

} catch(err) {
// Return the error
next(err);
}
});

// Export the Model
module.exports = mongoose.model('User', user);

Now that there’s a function for saving the password securely we can also add a function to compare the password securely using Argon2 hashes. This can reduce some code for the rest of the project as well.

Models/User.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Create a function for comparing a password with our stored hash
user.methods.comparePassword = function(candidate, next) {
try {
// Use Argon2.verify to check if the hash and
// canidate passwords match
if (await argon2.verify(this.password, candidate)) {

// If they do match, return true as the result
return next(null, true);

} else {

// If they don't match, return false as the result
return next(null, false);
}

} catch(err) {
// Return the error
next(err);
}
};