How to Auto-Hash Passwords in Mongoose

Updated: December 30, 2023 By: Guest Contributor Post a comment

Introduction

When dealing with user authentication in any web application, safeguarding users’ passwords is of utmost importance. In the realm of MongoDB and Node.js, Mongoose is a powerful tool for object data modeling (ODM) that simplifies interactions with the database. This tutorial explains how to automatically hash passwords using Mongoose whenever user data is saved or updated. The guide assumes you have basic knowledge of JavaScript/TypeScript, Node.js, and MongoDB operations.

Setting Up Mongoose

Before we delve into password hashing, ensure you have a Node.js environment set up with Mongoose installed:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/my_database');

We will use ES modules in this tutorial, so use the import syntax throughout the guide.

Creating the User Schema

We start by defining a user schema which represents the structure of the user document in MongoDB:

import mongoose from 'mongoose';
const { Schema } = mongoose;

const userSchema = new Schema({
  username: String,
  password: String // We will hash this field before saving
});

Utilizing Middleware for Password Hashing

Mongoose middleware (also known as ‘pre’ and ‘post’ hooks) allow you to execute functions before or after certain actions. We’ll use a ‘pre’ hook to hash the password before it’s saved:

import bcrypt from 'bcrypt';
import mongoose from 'mongoose';

const saltRounds = 10;
const userSchema = new Schema({username: String, password: String});

userSchema.pre('save', async function(next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, saltRounds);
  }
  next();
});

const User = mongoose.model('User', userSchema);

In this code snippet, we use bcrypt to hash our password with a certain number of salt rounds.

Handling password updates securely

To ensure password hashes are updated securely when a user updates their password, you can use instance methods:

userSchema.methods.setPassword = async function(password) {
  this.password = await bcrypt.hash(password, saltRounds);
};

userSchema.methods.checkPassword = async function(password) {
  return await bcrypt.compare(password, this.password);
};

// Updating a user's password
const user = await User.findById(userId);
await user.setPassword('newPassword123');
await user.save();

Integrating Mongoose Plugins

For advanced cases, you can create or use existing Mongoose plugins that handle password hashing, providing a reusable solution:

import bcrypt from 'bcrypt';
import mongoose, { Schema } from 'mongoose';

const saltRounds = 10;

const passwordPlugin = (schema, options) => {
  schema.pre('save', async function(next) {
    if (this.isModified('password')) {
      this.password = await bcrypt.hash(this.password, saltRounds);
    }
    next();
  });

  schema.methods.setPassword = async function(password) {
    this.password = await bcrypt.hash(password, saltRounds);
  };

  schema.methods.checkPassword = async function(password) {
    return await bcrypt.compare(password, this.password);
  };
};

const userSchema = new Schema({ username: String, password: String });
userSchema.plugin(passwordPlugin);

const User = mongoose.model('User', userSchema);

By applying this plugin, the User schema now has automatic password hashing, as well as methods for setting a new password and checking passwords.

Testing Password Hashing with Mocha/Chai

Ensuring that our password hashing works, unit testing with Mocha and Chai is imperative:

import { User } from './user'; // import our User model
import chai from 'chai';
const expect = chai.expect;

describe('User model', () => {
  it('should hash the password before saving', async () => {
    const user = new User({username: 'test', password: 'password'});
    await user.save();
    expect(user.password).to.not.equal('password');
  });
})

This simple test writes a password to the User and asserts that, once saved, it no longer matches the original plain text value.

Conclusion

Auto-hashing passwords within Mongoose middleware establishes a consistent security measure across your application. By following the practices outlined in this tutorial, you can ensure your password data is managed safely and effectively. Remember, security best practices are constantly evolving, so keep up to date on the latest methodologies to protect your users.