Reset/Forgot password on a rest api with jsonwebtoken (jwt) in express
Tags: express, api, backend, webdev
Published: 2023-05-27 20:02:00

## Overview

-   Add password reset functionality on top of a rest-api that has authentication and authorization implemented. If you want to know the basics of how jsonwebtoken is used for authorization, you can read [here](https://dev.to/renzhamin/authenticate-and-protect-rest-api-routes-with-jwt-and-refersh-token-rotation-1lg5)
-   Prisma ORM is used with SQLite. So you don't have to setup any database in your system
-   This approach is stateless and requires the least amount of db calls, therefore prioritizing performance
-   The code is available on [github](https://github.com/renzhamin/rest-api-base/tree/pass-reset)

### Flow


![Sequence Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/czwqxaky64n8gk31fdq3.png)





## Table of Contents

{%- # TOC start (generated with https://github.com/derlin/bitdowntoc) -%}

- [Token Generation](#token-generation)
  * [Why keep the validity duration small ?](#why-keep-the-validity-duration-small-)
  * [Why not just use regular accessToken ?](#why-not-just-use-regular-accesstoken-)
- [Sending Email](#sending-email)
  * [How to enable app password in google account](#how-to-enable-app-password-in-google-account)
  * [Sending the reset link](#sending-the-reset-link)
- [Resetting password](#resetting-password)
  * [Password Reset Form](#password-reset-form)
  * [Updating password](#updating-password)

{%- # TOC end -%}


## Token Generation

utils/genToken.ts:

```ts
// helper function to generate token
export const genToken = (
    user: any,
    secret: string,
    expiresIn: string,
    tokenId?: string
) => {
    const { id, username, email } = user
    const jwtid = tokenId || randomUUID()
    const token = jwt.sign({jwtid, id, username, email}, secret,{
        algorithm: "HS256",
        expiresIn,
    })

    return token
}
```

-   Generate a token with a 2 minute expiration time (lesser the better)
-   Use an environment variable as secret, this should be large and random

```ts
export const genPassResetToken = (user) => {
    return genToken(user, process.env.PASS_RESET_TOKEN_SECRET!, "2m")
}
```

### Why keep the validity duration small ?

As we are using stateless jwt, we can not invalidate the token. To prevent a user from using the same link again and again, we must set the expiration time to a lower value

### Why not just use regular accessToken ?

A regular access token provides access to protected resources which we don't want at all.
The user only provides a email address, they may or may not be a legitimate user. Thats why we can't just hand over an accessToken on a password reset request

## Sending Email

There are multiple ways to send email from a server.

Here is a sample code to setup a gmail account to send an email using nodemailer.

utils/sendEmail.ts:

```ts
export const sendEmail = async (receiverEmail, subject, body) => {
    const transporter = nodemailer.createTransport({
        service: "gmail",
        auth: {
            // your gmail address
            user: process.env.serviceEmail,
            // app password for the gmail
            pass: process.env.serviceEmailPassword,
        },
    })

    const mailOptions = {
        from: process.env.serviceEmail,
        to: receiverEmail,
        subject: subject,
        html: body,
    }

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            console.log("Failed to send email to", receiverEmail)
        } else {
            /* console.log("Email sent: " + info.response) */
        }
    })
}
```

### How to enable app password in google account

1. Go to [google account page](https://myaccount.google.com)
2. Navigate to security and enable 2-step verification
3. Search "App Passwords" in the search bar and click on the matched result
4. Chose app as "Mail" and device as "Other" and put anything there
5. Click on "GENERATE" button and save the password as `serviceEmailPassword` in .env file. (NOTE: you only get one chance to save the password)

### Sending the reset link

controllers/passReset.ts:

```ts
// Route: GET /api/pass-reset; expecting email in request body
export const getPassResetLink = async (req, res) => {
    try {
        if (!req.body.email) {
            return res.status(400).json({ msg: "No email provided" })
        }
        const user = await findUserByEmail(req.body.email)
        if (!user || !user.email) return res.json({ msg: "Email not found" })
        const token = genPassResetToken(user)
        const requrl = req.protocol + "://" + req.get("host") + req.originalUrl
        // the url from which the request came from, in local environment it is,
        // http://localhost:5000/api/pass-reset

        // if the frontend is in different domain declare PASS_RESET_URL in .env file
        const url = process.env.PASS_RESET_URL || requrl
        const resetLink = `<a target='_blank' href='${url}/${user.id}/${token}'>Password Reset Link</a>`
        sendEmail(req.body.email, "Reset Password", resetLink)
        res.json({ msg: "Password Reset Link sent to email" })
    } catch (error) {
        res.status(500).json({ msg: "Failed to send email" })
    }
}
```

## Resetting password

### Password Reset Form

-   The frontend should have a route "PASS_RESET_URL/:userId/:token" that takes an email in the body which sends a POST request to `/api/pass-reset/:userId/:token` on submit
-   Here, I have created the most basic html form to keep this article backend focused

utils/passReset.ts:

```ts
// Route : GET /api/pass-reset/:userId/:token
export const getPassResetPage = (req, res) => {
    const { userId, token } = req.params

    res.send(`<form action="/api/pass-reset/${userId}/${token}" method="POST">
             <input type="password" name="password" value="" placeholder="Enter your new password..." /> 
             <input type="submit" value="Reset Password" />
             </form>`)
}
```

### Updating password

Route : `router.post("/api/pass-reset/:userId/:token", verifyPasswordResetToken, passReset)`

-   verifyPasswordResetToken middleware verifies the token with the secret PASS_RESET_TOKEN_SECRET

utils/passReset.ts:

```ts
export const passReset = async (req, res) => {
    try {
        const { password } = req.body
        if (!password) return res.send("Password not provided")
        updatePassword(req.params.userId, password)
        res.json({ msg: "Password Reset Successfull" })
    } catch (error) {
        res.status(404).send("Failed to reset password")
    }
}
```

-   IMPORTANT: Hash the password before saving it in the database
-   Ensure that you use the same hash function for password hashing in every cases such as user creation, change password, reset password


db/users.ts:

```ts
export const updatePassword = async (userId: string, newPassword: string) => {
    newPassword = await hashString(newPassword)
    return db.user.update({
        where: { id: userId },
        data: { password: newPassword },
    })
}
```

Find me on
💻 [Github](https://github.com/renzhamin)
🔘 [LinkedIn](https://www.linkedin.com/in/renzhamin)
⭕ [Twitter](https://twitter.com/renzhamin)