Sling Academy
Home/Node.js/How to Auto-Hash Passwords in Mongoose

How to Auto-Hash Passwords in Mongoose

Last updated: December 30, 2023

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.

Next Article: How to set default values in Mongoose

Previous Article: How to auto-generate timestamps in Mongoose

Series: Mongoose.js Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates