Verify a User via SMS with Express and Twilio Verify

September 13, 2021
Written by

verify sms express.png

Many applications verify and authorize their users by sending a numeric code, called a One-Time Passcode (OTP), to the user’s phone number via either a voice call or a text message.

In this article you’ll learn how to perform user verification via SMS through Twilio Verify. You’ll build a pared down frontend using JavaScript and HTML and you’ll build a Node.js backend with Express.

Prerequisites

To get started with this tutorial, you’ll need the following:

  • Node.js installed on your machine, along with a package manager like npm or yarn
  • A free Twilio account (sign up with this link and get $10 in free credit when you upgrade your account)
  • A phone where you can receive text messages

Scaffold your backend

In your terminal or command prompt window, navigate to your main projects or development folder. From there, run the following commands to create a new project folder, initialize a new Node.js app, and install the Twilio Node Helper Library along with other dependencies:

mkdir verify-sms-express
cd verify-sms-express
npm init -y
npm install express twilio dotenv

Create a .env file with your Twilio credentials

Inside your new verify-sms-express app, create a new file called .env. You’ll store your Twilio Account SID and Auth Token along with one other credential in this file. Copy and paste the following text in your .env file:

TWILIO_ACCOUNT_SID=XXXX
TWILIO_AUTH_TOKEN=XXXX
VERIFY_SERVICE_SID=XXXX

The XXXX represents placeholder values that you will replace shortly, so leave this file open for now.

Navigate to the Twilio Console. You’ll be able to find your Account SID and Auth Token on the main dashboard of the console. Copy these values and paste them into your .env file as the values for TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN respectively.

In order to use Twilio Verify, you also need to set up a new Twilio Verify service. A service is a set of configurations you can use to manage verifications on your app or website.

Creating a new Verify service

Navigate to the Verify section of the Twilio Console and select Services from the left-hand menu. Click the blue plus sign (+) to create a new service. A modal dialog will pop up where you can enter a name for your service. Call it “node-demo”, or any other short, descriptive name of your choosing.

After creating your service, you’ll be taken to a general settings page for your new service.

Twilio Service General Settings page

Below your service name, you will see your SERVICE SID. Copy this value and paste it into your .env file, replacing the XXXX placeholder for the VERIFY_SERVICE_SID variable.

Save and close your .env file.

Back in your browser, keep scrolling down the Verify Service General Settings page to the bottom to find the Delivery Channels section. Since you are only going to use SMS in this example, it is a good idea to disable the other two channels.

screenshot of verify service settings page showing call enabled and sms and email disabled

Click the Save button to record your changes. Congratulations, your SMS verification service is now fully configured and you are ready to start coding the application!

Build the frontend

In this section you are going to set up your app’s frontend.

Run the following commands from your verify-sms-express folder to create your frontend file structure:

mkdir views
cd views
touch index.html

Open your new index.html file in your favorite text editor and add the following HTML:

<!DOCTYPE html>
<html>
  <head>
    <title>Verify SMS Demo</title>
  </head>

  <body>
    <form id="phone-form">
      <h2>Enter your phone number with country code:</h2>
      <input type="tel" id = "phone-number-input" placeholder="e.g. 15551235555"/>
      <input id="phone-submit" type="submit"/>
    </form>

    <form id="verify-form">
      <h2>Enter your verification code:</h2>
      <input type="number" id="otp-input" placeholder="e.g. 123456"/>
      <input id="verify-submit" type="submit"/>
    </form>

    <div id="response-text"></div>
  </body>

  <script type="text/javascript" src = "script.js"></script>
</html>

This HTML creates two forms: one for the user to enter their phone number, and one for the user to enter the verification code they will receive via SMS after submitting their phone number. It also creates an empty <div></div> element that will eventually display a success or failure message to the user.

You don’t want both of these forms visible at the same time, so add the following CSS in the <head></head> tags, below the <title> element, to hide the #verify-form (and the empty <div></div>) when the page loads:

<style>
  #verify-form,
  #response-text {
    display: none;
  }
</style>

Write the code to handle form submissions

In your terminal or command prompt, navigate back to the root directory of your project, verify-sms-express, and create a new folder called public:

mkdir public

Inside this folder, created a new file called script.js.

This file is where you will write the code to process the form submissions and connect with the Twilio API.

At the top of script.js, paste in the following code:

const phoneForm = document.getElementById('phone-form');
const verifyForm = document.getElementById('verify-form');
const responseText = document.getElementById('response-text');

let phoneNumber;

This code declares a few variables you’ll use through the script.

Beneath the variable declarations, add the following code to process any submissions to your #phone-form:

phoneForm.addEventListener('submit', async e => {
  e.preventDefault();

  phoneNumber = document.getElementById('phone-number-input').value;

  const response = await fetch('/send-notification', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({phoneNumber : phoneNumber })
  }).catch(e => console.log(e));

  if (response.ok) {
    phoneForm.style.display = 'none';
    verifyForm.style.display = 'block';
  }
});

This code listens for a submit event on the #phone-form. Whenever this submit event occurs, a callback function is run that handles the following tasks:

  • Prevents normal submission of the form
  • Captures the value of the phone number that the user entered in the #phone-number-input field
  • Makes a POST request to the app’s backend endpoint, /send-notification, as highlighted in the code above, with the phone number value
  • Switches the visible form upon a successful response from the request

Below all the existing code, add the following code to process the #verify-form submissions:

verifyForm.addEventListener('submit', async e => {
  e.preventDefault();

  const otp = document.getElementById('otp-input').value;

  const data = {
    phoneNumber: phoneNumber, 
    otp: otp
  };

  const response = await fetch('/verify-otp', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
    body: JSON.stringify(data)
  }).catch(e => console.log(e));

  const check = await response.json();

  const text = response.ok ? check.status : response.statusText;
  responseText.innerHTML = text;

  verifyForm.style.display = 'none';
  responseText.style.display = 'block';  
});

This code works similarly to the code you added in the previous step, but it is used during the verification phase. The primary difference is that this code sends the value of both the phone number and the user-submitted OTP to the app’s backend endpoint, /verify-otp.

When it receives a response from the POST request, it displays the verification status to the user via the #response-text element.

Save and close your script.js file. It’s time to build the backend!

Build the backend

The first step is to create a new file called index.js at the root of your project directory. This file is where all your backend code will go.

Open your new file in your text editor and add the following code:

const express = require('express');
const path = require('path');
require('dotenv').config();
const client = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

This code will load your dependencies and initialize the Twilio client.

Below the code you just added, paste in the following code:

const app = express();
const port = process.env.PORT || 3000;

app.use(express.static(__dirname + '/public'));
app.use(express.urlencoded({extended: true}));
app.use(express.json());

Next, add the code needed to load your frontend when you visit http://localhost:3000:

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '/views/index.html'));
});

In the frontend code above, you made POST requests to two backend endpoints: /send-notification and /verify-otp. You’ll create these endpoints now.

Send the verification code to the user

The /send-notification endpoint receives the user’s phone number, and using the Twilio Verify API, sends an SMS to the phone number with the OTP to the recipient. This initiates the verification process.

To create this endpoint, add the following below all the existing code in index.js:

app.post('/send-verification', async (req, res) => {
  client.verify.services(process.env.VERIFY_SERVICE_SID)
    .verifications
    .create({to: `+${req.body.phoneNumber}`, channel: 'sms'})
    .then(verification => console.log(verification.status))
    .catch(e => {
      console.log(e)
      res.status(500).send(e);
    });

  res.sendStatus(200);
});

The verification is initiated on the highlighted line in the code above. In the object passed to the create() method, you can add other properties to further customize the verification.

For example, if you wanted the call to be delivered in another language (Twilio supports over 30 languages for SMS and voice verification), you could add add the locale property:

.create({to: `+${{req.body.phoneNumber}`, channel: 'sms', locale: 'es'})

Verify the OTP submitted by the user

The /verify-otp endpoint receives both the user’s phone number, and the OTP that the user submitted. Using the Twilio Verify API, this endpoint checks to confirm that the submitted OTP is correct, and returns a response containing data about this verification check, including a status.

There are three statuses that a verification check can have: pending, approved, or canceled.

  • Pending verification checks refer to any verifications that have been initiated but that have not been approved, canceled, or expired.
  • Approved verification checks refer to verifications where the OTP has been confirmed to be a match.
  • Canceled verification checks are either canceled, or if it’s been 10 minutes or longer since the verification code was sent to the user, expired.

Below the /send-verification endpoint you just added, paste the following code:

app.post('/verify-otp', async (req, res) => {
  const check = await client.verify.services(process.env.VERIFY_SERVICE_SID)
    .verificationChecks
    .create({to: `+${req.body.phoneNumber}`, code: req.body.otp})
    .catch(e => {
      console.log(e)
      res.status(500).send(e);
    });

  res.status(200).send(check);
});

This code performs the verification check with the phone number and the OTP submitted by the user. It then sends a new response with the verification check data as the body of the response.

To finish your index.js file, beneath all the code, add these two lines:

app.listen(port);
console.log('Server started at http://localhost:' + port);

This code is responsible for starting your local server when the file is run.

Test out your app

It's time to test out the app! In your command line, navigate to the verify-sms-express directory if you’re not already there. Run the following command to start your local development server:

node index.js

This will start a local server on port 3000.

In your browser, navigate to http://localhost:3000/ to see your app.

Screenshot of app showing phone number input field

Enter your phone number in the field, starting with the country code, and then click the button that says Submit.

Once you click the Submit button, the phone number form will be replaced with the verification code form. A few moments later, you’ll receive an OTP via text message.

Back in your browser, submit the verification code you received in the verification form:

Screenshot of app showing OTP in input field

When you click the Submit button, the /verify-otp endpoint you created will be called. If you entered the correct code in the form, once the frontend of your app receives a response from the backend, you will see the “approved” status message:

screenshot showing approved verification check status

If you entered the wrong code, the message would say “pending” instead. In a production app, you would likely give the user an opportunity to enter the code again by showing the verify form a second time instead of the “pending” message.

What’s next for authenticating users with Twilio Verify?

Congratulations on implementing safe practices and incorporating security measures into your project!  

Twilio Verify supports several channels through which the user can be verified other than SMS, including voice and email. Consult the documentation to learn about these and other verification methods.

Let me know if you used Twilio Verify API to protect your users in your project by reaching out to me on Twitter!

Ashley is a JavaScript Editor for the Twilio blog. To work with her and bring your technical stories to Twilio, find her at @ahl389 on Twitter. If you can’t find her there, she’s probably on a patio somewhere having a cup of coffee (or glass of wine, depending on the time).