DEV Community

Felix Owino
Felix Owino

Posted on

Hashing and Verifying Passwords in Mongoose Schema

Are you creating a web application that is going to require users to sign in with passwords? It's a good security measure to store passwords in the database as hashed strings instead of plain strings. Hashing passwords is done to protect users' passwords. In case an attacker gains access to your database, they will see the hashed passwords but they won't be able to use them.

In this article, we will learn how to create a user schema that hashes users' passwords before saving them to the database. We will also learn how to add an instance method to the schema that verifies user passwords during login.

We will not cover other surrounding topics such as user authentication and user registration middleware in this article. The main goal of this article is to show you how to create a self-contained schema. This schema will make it easy for you to implement user registration and authentication middleware without worrying about password hashing.

We will use Typescript in this article. This article does not go into depth about how to create models and schema with Mongoose and Typescript. Therefore, this article assumes that you know how to create Mongoose schema using Typescript. If you are new to using Typescript, visit this article on creating Mongoose schema with Typescript before continuing.

Before we dive into coding, let us list out the procedure we are going to follow.

  1. Initialize a working directory and configure Typescript.
  2. Create the user schema.
  3. Implement password hashing.
  4. Implement password Verification.

Let us start by creating a working directory.

1. Initializing working directory and configure Typescript.

In this step, we will clone a pre-configured working directory from GitHub. You can also create your own directory with configurations of your choice. If you need help, see how to set up an initial project directory for Node and Typescript.

Your working directory should have something looking like this.

Initial working directory

After successfully cloning the repository, install its dependencies using the following command:

npm install
Enter fullscreen mode Exit fullscreen mode

With the working directory set up, let us create the user schema.

2. Creating the user schema.

To create a Mongoose schema, we need to install mongoose and import it to our models file.

Install mongoose using the following command:

npm install mongoose
Enter fullscreen mode Exit fullscreen mode

Create the User.model.ts file in the src directory of your project and import Mongoose.

import mongoose from "mongoose"
Enter fullscreen mode Exit fullscreen mode

Create an interface for the raw document as created in the code snippet below.

interface IUser{
    first_name: string,
    last_name: string,
    password: string,
    username: string
}
Enter fullscreen mode Exit fullscreen mode

Create the user schema that implements the interface as created in the code snippet below.

const userSchema = new mongoose.Schema<IUser>({
    first_name: String,
    last_name: String,
    password: String,
    username: String
})
Enter fullscreen mode Exit fullscreen mode

Now we have successfully created a user schema, let's add password hashing logic in step 3.

3. Implement password hashing.

In the last step, we implemented a simple user schema. Here, we will add a functionality that will hash every password entered by the user before saving.

To add a logic that is executed before saving a document, we use the pre hook of the Mongoose Schema.

We will use the bcrypt module to hash user passwords. Install the bcrypt module and its types using the following commands.

npm install bcrypt
npm install -D @types/bcrypt
Enter fullscreen mode Exit fullscreen mode

Import the hash function from bcrypt as follows.

import { hash } from 'bcrypt'
Enter fullscreen mode Exit fullscreen mode

The following code snippet shows how the password hashing on save can be implemented on the schema.


userSchema.pre('save', async function(next){

    const hashedPassword =  await hash(this.password, 10)
    this.password = hashedPassword

    next()
})
Enter fullscreen mode Exit fullscreen mode

The above code snippet does the following:

  • Convert the raw password into a hash string.
  • Replace the original raw password with the hashed password.
  • Call the next callback function. The callback signals Mongoose that we are done hashing the password and it can now be saved to the database.

At this point, we are done with the password hashing functionality. Let us add an instance method to verify passwords in the next step.

4. Implement password verification.

In the previous section, we added password hashing to our schema. In this section, we will add a password verification method to the user schema.

Before adding the method to the schema, we need to create an interface for instance methods. The following code snippet shows the declaration of the interface with the signature for the verification function.

interface UserMethods{
    isValidPassword:(password: string) => Promise<boolean>
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to add the interface to the Schema constructor and Model type as follows.

type UserModel = Model<IUser,{}, UserMethods>

const userSchema = new mongoose.Schema<IUser, UserModel, UserMethods>({
    first_name: String,
    last_name: String,
    password: String,
    username: String
})
Enter fullscreen mode Exit fullscreen mode

Finally, we implement the isValidPassword method as follows:

Import the compare function and implement the method.

import { compare } from 'bcrypt'
Enter fullscreen mode Exit fullscreen mode
userSchema.method('isValidPassword', async function(
    password: string): Promise<boolean>{
    const isValid = await compare(password, this.password)
    return isValid
})
Enter fullscreen mode Exit fullscreen mode

Export the model as follows.

export const User = model<IUser, UserModel>('User', userSchema)
Enter fullscreen mode Exit fullscreen mode

This method will be accessible through the user document. In the implementation of authentication, this method can be called on the user document. The method returns a promise. The promise resolves to true or false depending on whether the user entered the correct password or not.

Conclusion

Finally, we have learned how to create a self-contained user schema. The schema can convert a password into a hash string before saving it to the database. The schema can also verify user passwords against the stored hashed passwords during authentication. Visit this article to learn more about creating Mongoose models with Typescript.

Top comments (0)