Password reset flow with Nodejs, Expressjs and SendGrid Email
Tags: passwordflow
Published: 2021-01-22 11:17:43

I implemented a password reset flow in a personal project and I thought I should write about it. This article assumes you can set up an express server, query a database, create routes... and all that fun stuff.

Packages we'd use:
``` 
SendGrid (for emails) - npm i @sendgrid/mail
Knex (A Database Query Builder. Of course you can use any) - npm i pg knex
uuid (to generate unique ids) - npm i uuid
bcrypt (to hash passwords) - npm i bcryptjs

```
Go ahead and install them!

### First I'd explain the process:

##### Step 1: 
- Create a 'password_reset' table in your database with columns "id", "email".

##### Step 2: 
- Users submit their email requesting for a password change, we check if this email exists in our "users" table (existing users table). If the user exists, we insert a unique id and the submitted email into the "password_reset" table and send them an email with a link containing that id.

##### Step 3:
- We use the id in the link to verify the user and update with the newly entered password

Easy stuff! Now let's write some code.

```
#### Step 2

//imports

const knex = require("--your db setup--")
//sendgrid for emails
const sgMail = require('@sendgrid/mail');
//uuid
const {
    v4: uuid
} = require("uuid")

//add your api key. Please sign up on https://sendgrid.com to get yours
sgMail.setApiKey(--Your API key--);

router.post("/password_reset", async (req, res)=> {
         //email user submitted

        const {
            email
        } = req.body

        try {
            //check if user exists

            const user = await knex("users").select("email").where({
                email
            })

            if (user.length > 0) {
            //generate a unique id

                let id = uuid()

            //insert into the "password_reset" table

                await knex("password_reset").insert([{
                    id,
                    email: user[0].email
                }])

            //email content sent with SendGrid. The unique id is sent in the email as a link

                const content = {
                    to: email,
                    from: "support@me.com",
                    subject: "Password Recovery",
                    html: `<body>
                    <p>Click to set a new password : <a href="yoururl/password/reset/${id}">Reset password</a></p>
                   
                    </body>`
                }
                await sgMail.send(content)
                return res.send("Please check your email for the next step")
            }

          //if email doesn't exist

            return res.status(404).send("email does not exist in our records")
        } catch (error) {
            res.send(error.message)
        })

```


Next step is to verify the user by checking the "password_reset" table with the id passed in the email url.

```
#### Step 3A

  router.get("/verify_email/:id", async (req, res) => {
       //pass in the id as a parameter

        const {
            id
        } = req.params

        try {
           //query for the email with the provided id

            const email = await knex("password_reset").select("email").where({
                id
            })
           //send back the email

           return res.send(email[0])

        } catch (error) {
            res.status(404).send(error.message)
        }
}
)

```


Finally We update the user's password in the "users" table, and clear the record in the "password_reset" table to ensure the link sent in the email in "Step 2" is useless.

```
##### Step 3B:

router.post("/change_password", async(req,res)=>{
        const {
            email,
            id,
            newpassword
        } = req.body

        try {
            //final check if user exists in "password_reset" table

            const user = await knex("password_reset").select("email").where({
                email
            })
            if (user.length === 0) {
                return res.status(404).send("an error occured")
            }

            //hash new password

            const salt = await bcrypt.genSalt(10)
            const hashedpassword = await bcrypt.hash(newpassword, salt)
            
           //update the users table!

            await knex('users')
                .where({
                    email
                })
                .update({
                    password: hashedpassword
                })
             
            //clear the record in "password_reset" table for security

            await knex('password_reset')
                .where({
                    id
                })
                .del()

            return res.send("password successfully changed!")
        } catch (error) {
            res.send(error.message)
        }
})
```

The End!

### Note: 
- There are probably other ways to do this, this is what I came up with.