How To Implement A Password Reset Feature In Your NodeJS Application
Tags: javascript, tutorial, webdev, programming
Published: 2022-10-25 20:02:03

In my article today, I will show you how you can implement a fully functional password reset feature in your NodeJS application.

## GETTING STARTED

### THE CRYPTO MODULE

We will be using the built-in node module `crypto` to generate a hash for the password reset link. If this module does not come with your node version, use the command below to install it.

```shell
$ npm i crypto
```
If you have installed the module / verified that the module exists already, we may proceed with the next step

## THE MODEL FILE

Head over to the `user model` and add the methods below to the schema object. 

```javascript
//generate password reset hash
userSchema.methods.generatePasswordResetHash = function(){
    //create hash object, then create a sha512 hash of the user's current password 
    //and return hash
    const resetHash = crypto.createHash('sha512').update(this.password).digest('hex')
    return resetHash;
}

//verify password reset hash
userSchema.methods.verifyPasswordResetHash = function(resetHash = undefined){
    //regenerate hash and check if they are equal
    return this.passwordResetHash() === resetHash;
}
```
### THE FIRST METHOD

This method `generatePasswordResetHash()` will generate and return a `sha512 hash` using the user's current password. 

> If in your database, the user's password is hashed already, you must modify the method to use the hashed password instead.

So you will do something like this

```javascript
//generate password reset hash
userSchema.methods.generatePasswordResetHash = function(){
    //create hash object, 
    //then create a sha512 hash of the user's hashed password 
    //and then return hash
    const resetHash = crypto.createHash('sha512').update(this.hash).digest('hex')
    return resetHash;
}
```

So if you want to use this method, create a new instance of the model and then pass in the user's document as an argument so that it will extract the user's hashed password or the user's password (if you stored it in plain text)

### THE SECOND METHOD

This method `verifyPasswordResetHash()` will regenerate the hash again and then compare it against the submitted hash from the URL.

Now you need to set up the HTML pages needed for this feature. If you have one set up already, you may skip the step below.

## THE HTML TEMPLATES

### reset.html

This HTML page will have a form that will send the password reset link to the user.

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reset Your Password</title>
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
    <!-- MDB -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/5.0.0/mdb.min.css" rel="stylesheet" />

    <style>
        .body{
            font-family: "Roboto", sans-serif;
        }
        .container{
            box-shadow: 0px 0px 8px #ddd
        }
    </style>
</head>

<body>
    <section class="container p-4 mt-5" style="max-width:500px">
        <h4 class="text-center mb-4">RESET YOUR PASSWORD</h4>
        <form method="post" action="/reset" style="max-width:500px;">
            <div class="mb-3">
                <label class="form-label">Email Address</label>
                <input type="email" name="email" class="form-control" placeholder="simon@webdev.com">
            </div>
            <div class="mb-3">
                <button type="submit" class="btn btn-primary">Send Reset Link</button>
            </div>
        </form>
    </section>
</body>

</html>
```

### new_pass.html

This HTML page will have a form that will enable the user to enter new password.

```html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Enter New Password</title>
    <!-- Google Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
    <!-- MDB -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/5.0.0/mdb.min.css" rel="stylesheet" />

    <style>
        .body{
            font-family: "Roboto", sans-serif;
        }
        .container{
            box-shadow: 0px 0px 8px #ddd
        }
    </style>
</head>

<body>
    <section class="container p-4 mt-5" style="max-width:500px">
        <h4 class="text-center mb-4">ENTER NEW PASSWORD</h4>
        <form method="post" action="/reset-pass" style="max-width:500px;">
            <div class="mb-3">
                <label class="form-label">New password</label>
                <input type="password" name="pass" class="form-control" placeholder="*****">
            </div>
            <div class="mb-3">
                <label class="form-label">Re-enter password</label>
                <input type="password" name="conpass" class="form-control" placeholder="*****">
            </div>
            <div class="mb-3">
                <button type="submit" class="btn btn-success">Reset</button>
            </div>
        </form>
    </section>
</body>

</html>
```

So this is an example of a password reset request form


![password-reset-form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/enh75r7812kp1ohun4qw.png)



Now here is the logic 😃

When a user enters his email address and requests for a password reset link, the system will check if the email address exists. 

If the email address exists, the application should generate a hash using the user's current password or the user's hashed password if the password is hashed before they are saved, then I will attach this hash with the user's email address to the password reset link

But if the email address does not exist, the application should respond with the message "Email address does not exist".

Now, when the user opens the password reset link in a browser, we will extract the user's email address and the hash from the URL. Then we will check again if the email address exists and if the hash is valid.

If the hash is valid, we will then issue a password reset form.

## THE SERVER FILE

Now let us create a `POST` route  that will generate a password reset link and send to the user's email address.

In our password reset link, we will attach an Identifier that will help us to identify the particular user that is requesting for a password reset. Our Identifier in this case is the **user's email address**, and when we generate a password reset link for the user, it will look like the format below;

- localhost:5000/reset?email=me@you.com&hash=122JNn2bi333

Getting interesting right?

Let's code it

In your server file, add the code below

```javascript
app.post('/reset', async (req, res) => {
    try{
        //find a document with such email address
        const user = await User.findOne({email : req.body.email})
        //check if user object is not empty
        if(user){
            //generate hash
            const hash = new User(user).generatePasswordResetHash()
            //generate a password reset link
            const resetLink = `http://localhost:5000/reset?email=${user.email}?&hash=${hash}`
            //return reset link
            return res.status(200).json({
                resetLink
            })
            //remember to send a mail to the user
        }else{
            //respond with an invalid email
            return res.status(400).json({
                message : "Email Address is invalid"
            })
        } 
    }catch(err){
        console.log(err)
        return res.status(500).json({
            message : "Internal server error"
        })
    }
})
```

This is the result of the code above, when I submit my email address for a password reset link

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666109185952/a7d17xgbw.png)

> At this stage, you only need to send a mail to the user's email address with the password reset link. 

### A LITTLE EXPLANATION

Let's talk about the code above.

Remember that we added 2 schema methods in our `model` file. 

The first method `generatePasswordResetHash()` used above, generates a hash with the **user's current password** using the `sha512` algorithm and this hash will be included in our password reset link.

So what I basically did, was to create a new instance of the model and I passed in the user's document as an argument so that the method `generatePasswordResetHash()` can take in the password directly when it is invoked.

## WHAT HAPPENS NEXT?

Now, what happens when the user clicks on the reset link? 

![i-dont-know-what-happened.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1666264474205/TA4YZXQYX.gif)

That's a good question.

We need to set up / modify the already existing `GET` route for `/reset`. 

> Recall that this `route` serves a form that requests an email address from the user for the password reset link to be sent to.

We will check if the URL has a query that contains the email address & the hash, then we will extract them. This means that the URL must be in the format `localhost:5000/reset?email=me@you.org&hash=aws5e5c44`

```javascript
//reset route
app.get('/reset', async (req, res) => {
    try {
        //check for email and hash in query parameter
        if (req.query && req.query.email && req.query.hash) {
            //find user with suh email address
            const user = await User.findOne({ email: req.query.email })
            //check if user object is not empty
            if (user) {
                //now check if hash is valid
                if (new User(user).verifyPasswordResetHash(req.query.hash)) {
                    //save email to session
                    req.session.email = req.query.email;
                    //issue a password reset form
                    return res.sendFile(__dirname + '/views/new_pass.html')
                } else {
                    return res.status(400).json({
                        message: "You have provided an invalid reset link"
                    })
                }
            } else {
                return res.status(400).json({
                    message: "You have provided an invalid reset link"
                })
            }
        } else {
            //if there are no query parameters, serve the normal request form
            return res.sendFile(__dirname + '/views/reset.html')
        }
    } catch (err) {
        console.log(err)
        return res.status(500).json({
            message: "Internal server error"
        })
    }
})
```
### A LITTLE EXPLANATION

Let's talk about the code above.

Recall again that we added 2 schema methods in our `model` file. 

The second method `verifyPasswordResetHash()` used above, regenerates the hash with the **user's current password**, then compares this hash with the one from the URL. It returns `true` if they are equal or `false` if they are not. 

So what I basically did was to create a new instance of the model and pass in the current user's document as an argument, then I invoked the method `verifyPasswordResetHash()` and passed in the hash from the URL as an argument, for the method to regenerate a hash and compare to see if they are equal.

What happens if they are equal?

It means that the user truly requested for a password reset and what we need to do now is to save the user's email address in the session and issue a password reset form.

So this is a sample page that will be rendered when the hash is valid

![password-update-form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjnhom9z5l3mdku9ewss.png)


All you have to do now is;

- Create a new route that will read the form data
- Compare the two passwords 
- Then update the user's password using his email address stored in the session

After the user must have updated his password, the **password reset link** will become invalid because the hash will no longer be the same.

## EXTRA

I published an e-book that will help you understand how to perform **CRUD operations in MongoDB Using NodeJS And Express**. If you are a fan of my articles and you love how I write, I am sure that you will love this e-book too. 

Use the link below to get $5 off your purchase.

{% embed https://simonugorji.gumroad.com/l/crud-operations-in-mongodb-using-nodejs-and-express-framework/first-ebook %}

Here is a [Github repository](https://github.com/Octagon-simon/pass-reset-nodejs) implementing a basic password reset feature in NodeJS. Check it out and modify it to suit your project.

Thank you for reading

Image Credit: Freepik