Web-Based Secure Password Reset with tru.ID and Vonage
Tags: tutorial, javascript, programming
Published: 2023-03-22 15:55:39

The **tru.ID** [SubscriberCheck API](https://developer.tru.id/docs/subscriber-check) confirms the ownership of a mobile phone number by confirming the presence of an active SIM card with the same number. A mobile data session is created with a request to a **Check URL** unique to this SIM card. 

**tru.ID** then resolves a match between the phone number entered by the user and the phone number that the mobile network operator (MNO) identifies as the owner of the mobile data session.

Any website or mobile application that requires some form of authentication using an email or username and a password will have a password reset flow. Usually, this flow involves the user providing identifying information (email, username, phone number, etc.) and then receiving a code via email or SMS or being asked to answer a secret question. 

This is how phishing attackers often gain fraudulent access to online accounts by intercepting the code or secret answer. With tru.ID, this exploitation is no longer possible.

In this tutorial, you will start with a basic web application that doesn't yet provide any form of password reset flow.

As you progress through the tutorial, you will adapt your codebase first to check whether the phone number associated with a user’s account is a phone number from a mobile network operator supported by tru.ID. If the phone number is supported, then the tru.ID SubscriberCheck flow will begin – firstly, to check that the phone number and the data connection match, and secondly, to check the SIM card hasn't recently swapped.

By the end of this tutorial, you will have successfully implemented a password reset flow with tru.ID's SubscriberCheck as the primary method of multi-factor authentication, and Vonage's Verify API as the secondary. If the SubscriberCheck is the triggered flow, the user will have a seamless verification process with no action required and no attack vector.

## Before you begin

To follow along with this tutorial, you'll need the following:

- A [tru.ID Account](https://tru.id)
- Node.js installed locally on your computer
- A mobile phone with an active data SIM card

## Getting started

A [**tru.ID** Account](https://tru.id/) is needed to make the SubscriberCheck API requests, so make sure you've created one.

You're also going to need some Project credentials from tru.ID to make API calls. So sign up for a tru.ID account, which comes with some free credit. We've built a CLI for you to manage your [**tru.ID** account](https://tru.id/), projects, and credentials within your Terminal. To install the [**tru.ID** CLI](https://github.com/tru-ID/cli) run the following command:

```bash
npm install -g @tru_id/cli
```

Run `tru login <YOUR_IDENTITY_PROVIDER>` (this is one of `google`, `github`, or `microsoft`) using the Identity Provider you used when signing up. This command will open a new browser window and ask you to confirm your login. A successful login will show something similar to the below:

```bash
Success. Tokens were written to /Users/user/.config/@tru_id/cli/config.json. You can now close the browser
```

**Note:** The `config.json` file contains some information you won't need to modify. This config file includes your Workspace Data Residency (EU, IN, US), your Workspace ID, and token information such as your scope.

Create a new **tru.ID** project within the root directory with the following command:

```bash
tru projects:create password-reset-web --project-dir .
```

Make a note of these tru.ID credentials because you'll need them later in the tutorial.

> **Note**: The users set up in the database for test purposes are configured based on **tru.ID**'s [SubscriberCheck Sandbox](https://developer.tru.id/docs/basics#subscribercheck) conditions.

In your Terminal, clone the `starter-files` branch with the following command:

```bash
git clone -b starter-files git@github.com:tru-ID/web-based-password-reset-tutorial.git
```

If you're only interested in the finished code in `main`, then run:

```bash
git clone -b main git@github.com:tru-ID/web-based-password-reset-tutorial.git
```

The `starter-files` branch already contains some third-party libraries needed to get you started. So in your Terminal, within the project directory, run the following command to install these third-party libraries:

```bash
npm install
npx sequelize-cli db:migrate # to create the database, and run the migrations.
npx sequelize-cli db:seed:all # to seed the test users into the database.
```

First, you'll need to expose your project to the Internet for a callback. So to initialize the ngrok tunnel by running the following command:

```bash
npx ngrok http 3000
```

Next, to run the web server, run the following command in your Terminal, in the project directory:

```bash
npm start
```

Once you've started the server, you'll have also created a ngrok tunnel, which shows output similar to what's shown in the example below:

```
[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
Example app listening at http://localhost:4000
Starting Ngrok now
Ngrok connected, URL: https://<subdomain>.ngrok.io
```

Below is a screenshot of the landing page you'll be shown when you open `localhost:4000` or your ngrok URL in your browser:


![A screenshot of the demo application's landing page. A white background, with a grey bar at the top, labelled 'My Web App'. Below this, side by side, are two blue buttons, one labelled 'Login', the other labelled 'Register'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o65g6hf2ypet94gwr4nl.png)

The `Login` screen reached by clicking the `Login` button will show you a page similar to what's shown in the image below:


![A screenshot of the demo application's landing page. A white background, with a grey bar, labelled 'My Web App' at the top. Below is a form containing two fields, 'Email' and 'Password' with their appropriate labels, followed by a Blue button labelled 'Login' and a link 'I forgot my password.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pajiu9efgmm5psxy2fkb.png)

## Implementing tru.ID SubscriberCheck

### Install tru.ID Web SDK

To carry out a SubscriberCheck, we'll be using the **tru.ID** Web SDK. Install this SDK into your application with the following command:

```bash
npm install @tru_id/tru-sdk-web
```

### tru.ID Access Token

You'll want to avoid hardcoding credentials and sensitive information in your codebase; for this, we'll be using Environment Variables. Install `dotenv` package into your project:

```bash
npm install dotenv
```

Inside `src/server.js` find the line: `const port = 4000;` and below this add the following:

```javascript
require('dotenv').config()
```

Now, create a `.env` file in the root directory of your project and add the following:

```dotenv
TRU_ID_CLIENT_ID=
TRU_ID_CLIENT_SECRET=
EXTERNAL_URL=
```

>**Note:** Populate the values of the above with your own **tru.ID** client_id and client_secret, while the `EXTERNAL_URL` is your ngrok URL.

To make API requests to **tru.ID** you will need to generate an access token with your `client_id` and `client_secret`. First, create a new directory called `api` within your `src` directory. And within this new directory, create the `tru.js` file. Populate this file with the following:

```javascript
const moment = require("moment");
const fetch = require("node-fetch");
const httpSignature = require("http-signature");
const jwksClient = require("jwks-rsa");

const tru_api_base_url = 'https://eu.api.tru.id';
const keyClient = jwksClient({
  jwksUri: `${tru_api_base_url}/.well-known/jwks.json`,
});

// token cache in memory
const TOKEN = {
  accessToken: undefined,
  expiresAt: undefined,
};

async function getAccessToken() {
  // check if existing valid token
  if (TOKEN.accessToken !== undefined && TOKEN.expiresAt !== undefined) {
    // we already have an access token. Let's check if it's not expired
    // by removing 1 minute because the access token is about to expire, so it's better to refresh anyway
    if (
      moment()
        .add(1, "minute")
        .isBefore(moment(new Date(TOKEN.expiresAt)))
    ) {
      // token not expired
      return TOKEN.accessToken;
    }
  }

  const url = `${tru_api_base_url}/oauth2/v1/token`;

  const toEncode = `${process.env.TRU_ID_CLIENT_ID}:${process.env.TRU_ID_CLIENT_SECRET}`;
  const auth = Buffer.from(toEncode).toString('base64');

  const requestHeaders = {
    Authorization: `Basic ${auth}`,
    "Content-Type": "application/x-www-form-urlencoded",
  };

  const res = await fetch(url, {
    method: "post",
    headers: requestHeaders,
    body: new URLSearchParams({
      grant_type: "client_credentials",
      scope: "phone_check coverage subscriber_check",
    }),
  });

  if (!res.ok) {
    return res.status(400).body("Unable to create access token")
  }

  const json = await res.json();

  // update token cache in memory
  TOKEN.accessToken = json.access_token;
  TOKEN.expiresAt = moment().add(json.expires_in, "seconds").toString();

  return json.access_token;
}
```

### Reachability

#### Creating the Coverage Check

The first step is to determine whether the data connection from the device is a cellular data connection and is supported by tru.ID – a Coverage check. Within your `tru.js` file, add the following new method:

```javascript
async function checkCoverage(req, res) {
  const accessToken = await getAccessToken()
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

  const deviceCoverageResponse = await fetch(
    `${tru_api_base_url}/coverage/v0.1/device_ips/${ip}`,
    {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    },
  )

  if (deviceCoverageResponse.status === 200) {
    // Loop through products to double check we cover SubscriberCheck for this MNO
    const data = await deviceCoverageResponse.json()

    for (var counter = 0, length = data.products.length; counter < length; counter++) {
      if (data.products[counter] !== null) {
        if (data.products[counter].product_id === 'SUK') {
          return true
        }
      }
    }
  } else if (deviceCoverageResponse.status === 400) {
    // tru.ID has no coverage for this mobile network operator default to SMS
    console.log('tru.ID has no coverage for this mobile network operator. Sending an SMS OTP')
    return false
  } else if (deviceCoverageResponse.status === 412) {
    // Default to SMS
    console.log('IP address is not a mobile IP Address, here, you could suggest the user disabled WiFi. Sending an SMS OTP')
    return false
  } else {
    // Unexpected result from device coverage check. Default to SMS
    console.log('Unexpected result from coverage check. Sending an SMS OTP')
    return false
  }
}

module.exports = {
  getAccessToken,
  checkCoverage
}
```

Create a new routes file within `/src/routes` called `tru.js`. This file will contain the API routes your server needs to process the **tru.ID** SubscriberCheck flow. 

Within this new file, add the following functionality. This functionality takes the IP address of the device accessing the web page to determine whether it is on a mobile network IP address or tru.ID and the mobile network operator has coverage.

```javascript
const truApi = require('../api/tru')

async function checkCoverage(req, res) {
  try {
    const coverageCheckRes = await truApi.checkCoverage(req, res)

    return res.status(200).json({coverage: coverageCheckRes})
  } catch (error) {
    console.log(error)
    res.sendStatus(500)
  }
}

module.exports = {
  checkCoverage,
}
```

Now you need to expose this new endpoint through the server. So, open `/src/routes.js`, and at the top, add the import for your `tru.js` routes file:

```javascript
const tru = require('./routes/tru')
```

Within your `routes()` function, add the following definition for this `/api/coverage` endpoint, which will be called from the user's browser:

```javascript
  router.get('/api/coverage', tru.checkCoverage)
```

#### Calling the Coverage Check

To call this coverage check, you need to update the `password-reset` endpoint's template. Open `src/views/password-reset.hbs`, and at the bottom of the file, add the following:

```javascript
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tru_id/tru-sdk-web/dist/tru-id-sdk.umd.js"></script>

<script>
  // Step 1 - Do coverage check
  async function checkCoverage(ev) {
    try {
      const deviceCoverageResult = await axios.get('/api/coverage', {
        validateStatus: (status) => status >= 200 && status <= 412,
      })

      // If there's no coverage, prompt the user to turn off WiFi if it's enabled and recheck.
      if (deviceCoverageResult.status === 200 && deviceCoverageResult.data.coverage === true) {
        // tru.ID has coverage.
        await createSubscriberCheck(ev)
      } else if (deviceCoverageResult.status === 400 || deviceCoverageResult.status === 412) {
        // No coverage continue with POST request
        console.log('no coverage')
        return true
      } else {
        // Unexpected result, continue with POST request
        console.log('unexpected result')
        return true
      }
    } catch (ex) {
      // Unexpected result, continue with POST request
      console.log('error')
      console.log(ex)
      return true
    }
  }

  // Step 2 - If covered, do SubscriberCheck(based on the email address....)
  async function createSubscriberCheck(ev) {

  }

  document
    .getElementById('password_reset')
    .addEventListener('submit', async (event) => {
      const coverage = await checkCoverage()
      if (coverage !== true) {
        event.preventDefault()
      }
    })
</script>
```

The above code imports `axios` (A promise-based HTTP client for the browser) and tru.ID's `SDK`. Following the import, the code creates a function called `checkCoverage`. 

Within this function, a `GET` request is made to your `/api/coverage` endpoint to check whether the device is supported based on its IP address. At the bottom of this code, you'll see an event listener. This listener can only call the `checkCoverage` function when the user has submitted the button.


### SubscriberCheck

#### Creating the SubscriberCheck

The `SubscriberCheck` flow is as follows:

- 1 - Your Backend makes a `POST` request to **tru.ID** to create a check. 
- 2 - **tru.ID** returns in the response a `check_url`.
- 3 - Your web application in the user's browser opens that `check_url` in the background.
- 4 - This `check_url` is direct to that user's mobile network operator (MNO). The MNO verifies the request.
- 5 - Your backend makes a `PATCH` request to **tru.ID** with the `code` returned at the `redirect_url` defined when creating the check.

So, open your `/src/api/tru.js` file and add the following code for this functionality from your Backend's actions:

```javascript
async function createSubscriberCheck(phone) {
  // Create SubscriberCheck resource
  const token = await getAccessToken()

  const subscriberCheckCreateResult = await fetch(`${tru_api_base_url}/subscriber_check/v0.2/checks`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      phone_number: phone,
      redirect_url: `${process.env.EXTERNAL_URL}/api/sck-password-reset-code`,
    }),
  });

  if (subscriberCheckCreateResult.status === 201) {
    const data = await subscriberCheckCreateResult.json()

    return data
  }

  return false
}

async function patchSubscriberCheck(check_id, code) {
  const url = `${tru_api_base_url}/subscriber_check/v0.2/checks/${check_id}`
  const body = [{ op: 'add', path: '/code', value: code }]

  const token = await getAccessToken()
  const requestHeaders = {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json-patch+json',
  }

  const subscriberCheckPatchResult = await fetch(url, {
    method: 'PATCH',
    headers: requestHeaders,
    body: JSON.stringify(body),
  });

  if (subscriberCheckPatchResult.status === 200) {
    const data = await subscriberCheckPatchResult.json()

    return data
  }

  return {}
}

async function getSubscriberCheckStatus(checkId) {
  const url = `${tru_api_base_url}/subscriber_check/v0.2/checks/${checkId}`

  const token = await getAccessToken()
  const requestHeaders = {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  }

  const subscriberCheckResult = await fetch(url, {
    method: 'GET',
    headers: requestHeaders,
  });

  if (subscriberCheckResult.status === 200) {
    const data = await subscriberCheckResult.json()

    return data
  }

  return {}
}
```

Now update the exports:

```javascript
module.exports = {
  getAccessToken,
  checkCoverage,
  createSubscriberCheck,
  patchSubscriberCheck,
  getSubscriberCheckStatus
}
```

You may have noticed that you've added three functions. All of these will be used through the next steps of this tutorial.

Open `/src/routes/tru.js`; at the top, add the following two imports: 

```javascript
const db = require('../models')
const { getHashedPassword } = require('../utils/auth')
```

As previously explained, this file will handle all routes relevant to **tru.ID** and the SubscriberCheck. So within this file, as well as the `checkCoverage` function, you'll also have:

- createSubscriberCheck - This endpoint takes the phone number and `redirect_url`, making a `POST` request to tru.ID, requesting a `check_url` to be returned to the device.
- SubscriberCheckCallback - The endpoint for the previously defined `redirect_url`, where the web application will end up following the `check_url`. This request will contain the `request_id` and `code`, which will be sent as a `PATCH` request to **tru.ID** to complete the flow.
- getScPasswordResetCode - Checks the user has an active SubscriberCheck in progress, then renders the template form for the user to enter their email address and their new password.
- postScPasswordResetCode - Checks the user has an active SubscriberCheck in progress, then saves the user's new password. 

Within the `tru.js` file, add the following functions:

```javascript
async function createSubscriberCheck(req, res) {
  const { email } = req.body

  if (!email) {
    return res
      .status(400)
      .json({ error_message: 'email parameter is required' })
  }

  const user = await db.User.findOne({ where: { email: email } })

  if (!user) {
    return res.redirect('password-reset')
  }

  try {
    const subscriberCheckRes = await truApi.createSubscriberCheck(
      user.phone
    )

    if (subscriberCheckRes === false) {
      return res.sendStatus(400)
    }

    // Select data to send to client
    return res.status(200).json({
      check_id: subscriberCheckRes.check_id,
      check_url: subscriberCheckRes._links.check_url.href
    })
  } catch (error) {
    console.log(error)
    return res.sendStatus(500)
  }
}

async function subscriberCheckCallback(req, res) {
  const subscriberCheckResult = await truApi.patchSubscriberCheck(req.query.check_id, req.query.code)

  if (subscriberCheckResult === {}) {
    return res.redirect('/password-reset-code')
  } else if (subscriberCheckResult.match === false || subscriberCheckResult.no_sim_change === false) {
    // Not a match or sim changed, return to reset password with a message.
    var message = 'Unable to verify, please contact support.'
    var messageClass = 'alert-danger'

    return res.redirect(`/login?message=${message}&messageClass=${messageClass}`)
  } else {
    // Success! Move the user to the reset password form.
    return res.redirect(`/sc-password-reset-code?check_id=${req.query.check_id}`)
  }
}

async function getScPasswordResetCode(req, res) {
  const { check_id } = req.query

  if (!check_id) {
    return res.redirect('/login')
  }

  const subscriberCheckStatus = await truApi.getSubscriberCheckStatus(check_id)

  if (subscriberCheckStatus === {} || subscriberCheckStatus.match === false || subscriberCheckStatus.no_sim_change === false) {
    return res.redirect('/login')
  }

  res.render('sc-password-reset-code', { check_id: check_id })
}

async function postScPasswordResetCode(req, res) {
  const { check_id, email, password, password2 } = req.body

  if (!check_id) {
    return res.redirect('/login')
  }

  const subscriberCheckStatus = await truApi.getSubscriberCheckStatus(check_id)

  if (subscriberCheckStatus === {} || subscriberCheckStatus.match === false || subscriberCheckStatus.no_sim_change === false) {
    return res.redirect('/login')
  }

  const user = await db.User.findOne({ where: { email: email } })

  if (!user) {
     return res.render('login', {
      message: 'Invalid email',
      messageClass: 'alert-danger'
    })
  }

  if (password !== password2) {
    return res.render('login', {
      message: 'Passwords don\'t match',
      messageClass: 'alert-danger'
    })
  }

  const hashedPassword = await getHashedPassword(password)
  await user.update({ password: hashedPassword })
  await user.save()

  var message = 'Password has been reset, please log in.'
  res.redirect(`/login?message=${message}&messageClass=alert-success`)
}
```

Now update the exports at the bottom of the file:

```javascript
module.exports = {
  createSubscriberCheck,
  checkCoverage,
  subscriberCheckCallback,
  getScPasswordResetCode,
  postScPasswordResetCode
}
```

Now create the routes for these endpoints to be accessed. Open `/src/routes.js` and locate the line `router.get('/api/coverage', tru.checkCoverage)`. 

Below this line, add the following four new routes:

```javascript
  router.post('/api/subscriber-check', tru.createSubscriberCheck)
  router.use('/api/sck-password-reset-code', tru.subscriberCheckCallback)

  router.get('/sc-password-reset-code', tru.getScPasswordResetCode)
  router.post('/sc-password-reset-code', tru.postScPasswordResetCode)
```

You may have noticed a template called `sc-password-reset-code.hbs` in the new code above. You'll need this, so in `/src/views` create a new file called `sc-password-reset-code.hbs` and add the following:

```html
<div class="row justify-content-md-center" style="margin-top: 100px">
    <div class="col-md-6">

        {{#if message}}
            <div class="alert {{messageClass}}" role="alert">
                {{message}}
            </div>
        {{/if}}

        <form method="POST" action="/sc-password-reset-code">
            <input name="check_id" type="hidden" value="{{check_id}}">
            <div class="form-group">
                <label for="emailInput">Email address</label>
                <input name="email" type="email" class="form-control" id="emailInput" placeholder="Enter email">
            </div>
            <div class="form-group">
                <label for="passwordInput">New Password</label>
                <input name="password" type="password" class="form-control" id="passwordInput" placeholder="New Password">
            </div>
            <div class="form-group">
                <label for="passwordInput2">Re-enter your new Password</label>
                <input name="password2" type="password" class="form-control" id="passwordInput2" placeholder="Re-enter New Password">
            </div>
            <button type="submit" class="btn btn-primary">Reset my Password</button>
        </form>
    </div>
</div>
```

Now, back within `/src/views/password-reset.hbs`, you'll need the functionality to call these new routes and complete the `SubscriberCheck` section of the flow. Find the `Step 2` Line for the function `async function createSubscriberCheck(ev) {`. Within this function, add the following:

```javascript
    console.log('creating subscriber check')
    try {
      const emailInput = document.getElementById('emailInput')

      let emailValue = emailInput.value
      // strip spaces out of the phone number and replace them within an input
      emailValue = emailValue.replace(/\s+/g, '')
      emailInput.value = emailValue
      emailInput.blur()

      // Create SubscriberCheck resource
      const subscriberCheckCreateResult = await axios.post('/api/subscriber-check', {
        email: emailValue
      })

      if (subscriberCheckCreateResult.status === 200) {
        // Step 3 - Open Check URL. If successful, end up on `password-reset-code`. If it fails, call the fallback endpoint. (SMS)
        console.log('opening check url')
        console.log(subscriberCheckCreateResult)
        await tru.ID.openCheckUrl(subscriberCheckCreateResult.data.check_url, {
          checkMethod: 'image',
          debug: true,
          // we don't care here as we are already doing
          // the device coverage check automatically on page load
          // through the node server
          checkDeviceCoverage: false,
        })
      } else {
        return true
      }
    } catch (error) {
      console.log(error)

      if (error.response.status === 400) {
        console.log('Your Mobile Network is not supported or you may be on WiFi, if so disconnect from WiFi.')
      } else {
        console.log('An error occurred while creating a SubscriberCheck.')
      }
      return true
    }
```

The above piece of code first creates the SubscriberCheck; then, if you've successfully created a check, it will open the `check_url` returned from the API request and complete the flow. Otherwise, the user will be redirected to the Vonage Verify flow.

### Vonage Verification

If the device's IP address is not a mobile network or the network provided is not supported by tru.ID, a fallback is required. We'll use Vonage's Verification flow for this tutorial to send the verification code via SMS. However, you can define the workflow in Vonage's documentation.

To start implementing Vonage's Verify API into the flow, in your Terminal, in the project's root directory, run the following command:

```bash
npm install @vonage/server-sdk
```

Open your `.env` file and update it with the following extra variables. These variables are of your Vonage API credentials:

```dotenv
VONAGE_API_KEY=
VONAGE_API_SECRET=
BRAND_NAME=
WORKFLOW_ID=6 # SMS only.
```

Next, create a new file within the `api` directory to handle your Vonage API calls. Create `api/vonage.js` and populate this file with the following:

```javascript
const { Vonage } = require('@vonage/server-sdk')

async function createVerification(phoneNumber) {
  const vonage = new Vonage({
    apiKey: process.env.VONAGE_API_KEY,
    apiSecret: process.env.VONAGE_API_SECRET
  })

  const resp = await vonage.verify.start({
    number: phoneNumber,
    brand: process.env.BRAND_NAME,
    workflow_id: process.env.WORKFLOW_ID
  })  

  if (resp.status === '0') {
    // Success
    return resp.request_id
  }

  return null
}

async function verify(request_id, code) {
  const vonage = new Vonage({
    apiKey: process.env.VONAGE_API_KEY,
    apiSecret: process.env.VONAGE_API_SECRET
  })

  console.log(request_id, code)
  const verify = await vonage.verify.check(request_id, code)

  if (verify.status === "0") {
    // Success
    return verify.request_id

  }

  return null
}

module.exports = {
  createVerification,
  verify
}
```

This new file you've just created has two functions. The first is to initialise the Verify flow; the second is to handle the response from the user once they receive the code and enter it into the application.

For the Verify flow to work, you'll need to associate a user with a Verify `request_id`. So first, open `/src/models/user.js` and, within `User.init` add the following new field:

```javascript
requestId: DataTypes.STRING
```

Now in your Terminal, within the project's root directory, run the following command:

```bash
npx sequelize-cli migration:create --name modify_users_add_new_fields
```

Next, find the new file in `/src/migrations`. It will be named `<timestamp>-modify_users_add_new_field.js`.

Within the `async up()` of this file, replace the contents with:

```javascript
    return Promise.all([
      queryInterface.addColumn(
        'Users', // table name
        'requestId', // new field name
        {
          type: Sequelize.STRING,
          allowNull: true,
        },
      ),
    ]);
```

And within the `async down()` replace the contents with:

```javascript
    return Promise.all([
      queryInterface.removeColumn('Users', 'requestId'),
    ]);
```

In your Terminal, run the following command to run the migration:

```bash
npx sequelize-cli db:migrate
```

Locate the `/src/routes/auth.js` file and add the following import at the top of the file:

```javascript
const vonage = require('../api/vonage')
```

Next, within this file, find the function `postPasswordReset` and replace the last four lines of code:

```javascript
  res.render('login', {
    message: 'Reset Password Link sent via Email',
    messageClass: 'alert-danger'
  })
```

With the following:

```javascript
  const vonageRes = await vonage.createVerification(user.phone)

  if (vonageRes !== null) {
    await user.update({ requestId: vonageRes })
    await user.save()

    return res.redirect('password-reset-code')
  }

  return res.redirect('login')
```

You'll now need to handle the input from the user, which will contain their `email`, the verification `code`, and two copies of their `password`. Find `postPasswordResetCode()` and populate this function with the following:

```javascript
  const { email, password, password2, code } = req.body

  // Check if the user with the same email is also registered
  const user = await db.User.findOne({ where: { email: email } })

  if (!user) {
    res.redirect('/login?message=Invalid email&messageClass=alert-danger')
  }

  if (password !== password2) {
    res.render('register', {
      message: 'Passwords do not match.',
      messageClass: 'alert-danger'
    })
  }

  const vonageRes = await vonage.verify(user.requestId, code)

  if (vonageRes !== null) {
    const hashedPassword = getHashedPassword(password)

    await user.update({ requestId: null, password: hashedPassword })
    await user.save()

    let message = 'Password successfully reset. Please log in with your new credentials.'
    let messageClass = 'alert-success'

    return res.redirect(`/login?message=${message}&messageClass=${messageClass}`)
  }

  let message = 'No password-reset in progress. Please restart.'
  let messageClass = 'alert-danger'

  return res.redirect(`/login?message=${message}&messageClass=${messageClass}`)
```

## Wrapping up

That's it! You've now introduced a secure password reset flow within your web application using tru.ID's SubscriberCheck API, and a Vonage Verify backup in case the SubscriberCheck is not possible. 

The SubscriberCheck verifies that the user is the owner of that phone number, SIM card, and active data connection, and that the SIM card has not recently been swapped.

## Resources

- [tru.ID SubscriberCheck Integration guide](https://developer.tru.id/docs/subscriber-check/integration)
- [SubscriberCheck API Reference](https://developer.tru.id/docs/reference/products#tag/subscriber_check_v0.2)