How to use the Twilio Verify API with Firebase and React Redux

Satisfied colleagues improving application security by leveraging Verify and Firebase
January 18, 2023
Written by
Yi Tang
Twilion
Reviewed by
Paul Kamp
Twilion

In this post, we will discuss how to leverage Twilio Verify and Firebase to improve the security of your application. This is the first step in the build of our Twiliog platform, and we will show you the steps to implement sending SMS OTP (one-time passwords) to a user when they register, so we can verify the user’s phone number.

This will allow you to add an extra layer of security to our application, ensuring that only the right users have access to it. The backend of Twiliog is running on Firebase, while the front end uses React and Redux.

Prerequisites

Before we get started with the build, you’ll need to set up a few accounts and change a few settings.

  1. A Firebase Account - we’re using Firestore to store user information and Firebase Functions to run backend operations
    1. Create a new Project
    2. Create a Firestore Database
    3. Register an app and note the config information
Firebase config information


NOTE:
In order for all functionality to work, you’ll need a Blaze plan.
npm install

Start up the frontend with:

npm start

This should start up your localhost with your port of choice and show the sign in page:

Mock Twiliog signup page

Set up Firebase

Log into your Firebase console and select the project you created for this app.

Navigate to the your Firestore database, and update the Firebase Firestore rules with the following:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
          match /blogs/{blog}{
      allow read, write: if request.auth.uid != null
      }
      match /users/{userId}{
        allow create
        allow read: if request.auth.uid != null
        allow write: if request.auth.uid == userId
      }
  }
}

Navigate to Firebase Authentication. Enable authentication with email and password: https://firebase.google.com/docs/auth/web/password-auth

Connect App to Firebase

Back to our code, navigate to the functions folder under Twiliog.

Install the dependencies in the functions folder:

npm install 

Install Firebase CLI in the functions folder:

npm install -g firebase-tools

If you are running into issues around the version of node being used, this is a great resource that can help:

https://help.dreamhost.com/hc/en-us/articles/360029083351-Installing-a-custom-version-of-NVM-and-Node-js

Now, log into Firebase

firebase login

Select the project you want to use:

firebase projects:list

Switch to your new project with:

firebase use <projectname> 

Update the Verify service in the functions

There are two functions that we’ll use. One sends the verification code to the user, and the other checks the verification code provided by the user. In these two functions, we need to update the verify service with the service ID of the service you created in the prerequisites.

In Twiliog > functions > src > users > sendVerify.js, update the verify service ID in the API call:

client.verify.v2.services("VA…")

In Twiliog > functions > src > users > checkCode.js, update the verify service ID in the API call:

client.verify.v2.services("VA…")

Update the Twilio config information

In Twiliog > functions > src > .env, update the environment variables with your Twilio credentials:

TWILIO_ACCOUNT_SID=AC…
TWILIO_AUTH_TOKEN=...

Update the Firebase config information

In Twiliog > src > config > fbConfig.js. Update the firebaseConfig settings with your own project info:

const firebaseConfig = {
 apiKey: "...",
 authDomain: "...",
 projectId: "...",
 storageBucket: "...",
 messagingSenderId: "...",
 appId: "...",
 measurementId: "..."
};

Deploy the functions

Navigate back to your twiliog folder from the functions folder.

cd ..

To deploy the functions (the one used to send the Verify OTP code and the one used to check the code) to Firebase functions:

npm run functions:deploy

This will take a while but after they’re deployed, you’ll see the URL for the functions in the console. Note the URLs. We’ll need to update our frontend code with these.

In Twiliog > src > action > authActions.js, in the verify action, update the checkCode URL with the one you received when you deployed the functions:

axios.post("https://us-central1-yourprojectname.cloudfunctions.net/default-checkCode", userInfo, axiosConfig)

In the signUp action, update the sendVerify URL with the one you received when you deployed the functions:

axios.post("https://us-central1-yourprojectname.cloudfunctions.net/default-sendVerify", newUser, axiosConfig)

If anything goes wrong, you can view the Firebase logs with:

firebase functions:log

Architecture

This is a high-level overview of the various front end pages and how they interact with the Redux store, Firebase functions and Firestore, and Twilio Verify.

Architecture diagram verify authors Twiliog

Try it out

Let’s see how it works! Make sure your front end is running. If not, navigate to the Twiliog folder and use:


npm start
  1. Click the Sign up button in the top navigation bar
Mock signup page for Twiliog

 

  1. Enter your info, making sure to include your cell phone number in E-164 format, e.g., +15551112222
  2. Click SIGN UP
    You should receive a verification code via text shortly.
  3. Enter the verification code that you received at your phone number
Verification code input screen
  • If successful, you’ll be navigated to the home page
  • Blog posts

    You’ll notice that there are no blog posts when you’ve logged in. Try creating a new post and see what happens. You’ll also be able to see the Firestore in your Firebase console with the blog posts.

    Code Explanation

    Twilio’s Verify API makes it straightforward to add user verification to applications. We only need two API calls to do this:

    1. To send the verification code to the user
    2. To check the verification code provided

    When someone enters their info and clicks the Submit button on the signup page, our signUp action creator in the front end makes a call to our sendVerify.js function in Firebase. We pass along new user info, which includes the phone number, in the request to sendVerify.js.

export const signUp = (newUser) => {
   return (dispatch, getState, {getFirebase, getFirestore}) => {
     dispatch({type:'CHECK_VERIFY', payload:newUser})
     const axiosConfig = {
       headers: {
         'Content-Type': 'application/json;charset=UTF-8',
         "Access-Control-Allow-Origin": "*",
       }
     };
     axios.post("https://us-central1-projectname.cloudfunctions.net/default-sendVerify", newUser, axiosConfig)
     .then((result) => {
       console.log("signup result.data", result.data);
       dispatch({type: 'VERIFY_CODE_SENT_SUCCESS'});
       history.push('/verify');
     }).catch(err => {
       dispatch({type: 'VERIFY_CODE_SENT_ERROR', err})
     })
   }

Then sendVerify.js then uses Twilio's create Verification API to send an SMS to the provided phone number.

import * as functions from "firebase-functions";
const config = require("../../config");
const cors = require("cors")({origin: true});

export const sendVerify = functions.https.onRequest((request, response)=>{
 cors(request, response, () => {
   const phNumber = request.body.phNumber;
  
   const accountSid = config.TWILIO_ACCOUNT_SID;
   const authToken = config.TWILIO_AUTH_TOKEN;
   const client = require("twilio")(accountSid, authToken);

   client.verify.v2.services("VA…")
       .verifications
       .create({to: phNumber, channel: "sms"})
       .then((verification) => {
         console.log(verification.sid);
         response.send(verification.sid);
       })
       .catch((e) => {
         console.log(e);
         response.status(500).send(e);
       });
   response.sendStatus(200);
 });
});

When someone provides a code in the check verify page, the front end runs the verify action creator and calls the checkCode Firebase function. The request includes the user info, which has the phone number and entered code.

 export const verify =(userInfo)=>{
   return (dispatch,getState, {getFirebase, getFirestore}) => {
     const axiosConfig = {
       headers: {
         'Content-Type': 'application/json;charset=UTF-8',
         "Access-Control-Allow-Origin": "*",
       }
     };
     axios.post("https://us-central1-projectname.cloudfunctions.net/default-checkCode", userInfo, axiosConfig)
     .then((result) => {
       dispatch({type: 'VERIFY_SUCCESS'});
       
         if (result.data === "approved"){
         const firebase = getFirebase();
         const firestore = getFirestore();
         firebase.auth().createUserWithEmailAndPassword(
           userInfo.email,
           userInfo.password
         ).then((resp)=> {
           return firestore.collection('users').doc(resp.user.uid).set({
             firstName: userInfo.firstName,
             lastName: userInfo.lastName,
             initials: userInfo.firstName[0]+userInfo.lastName[0],
             email: userInfo.email,
             phNumber: userInfo.phNumber
           })
         }).then(() => {
           dispatch({type: 'SIGNUP_SUCCESS'})
         }).catch(err => {
           dispatch({type: 'SIGNUP_ERROR', err})
         })
       }
     }).catch(err => {
       dispatch({type: 'VERIFY_ERROR', err})
     })
   }
 }

In checkCode, we use the Verify API with the code and phone number to see if they match. If they do, the response back to the front end will include an approved.

import * as functions from "firebase-functions";
const config = require("../../config");
const cors = require("cors")({origin: true});
export const checkCode = functions.https.onRequest((request, response)=>{
 cors(request, response, () => {
   const accountSid = config.TWILIO_ACCOUNT_SID;
   const authToken = config.TWILIO_AUTH_TOKEN;
   const client = require("twilio")(accountSid, authToken);

   client.verify.v2.services("VA…")
       .verificationChecks
       .create({to: request.body.phNumber, code: request.body.passcode})
       .then((verificationCheck) => {
         response.send(verificationCheck.status);
       })
       .catch((e) => {
         console.log(e);
         response.status(500).send(e);
       });
 });
});

If approved (i.e., result.data === “approved”), we use a call to Firebase authentication to create the user in the verify action creator.

         firebase.auth().createUserWithEmailAndPassword(
           userInfo.email,
           userInfo.password
         ).then((resp)=> {
           return firestore.collection('users').doc(resp.user.uid).set({
             firstName: userInfo.firstName,
             lastName: userInfo.lastName,
             initials: userInfo.firstName[0]+userInfo.lastName[0],
             email: userInfo.email,
             phNumber: userInfo.phNumber
           })

Conclusion

This article has taken you through how we ensure only authorized users can sign up for our Twiliog application. We’ve shown you how you can implement Twilio Verify with Firebase Functions and Firestore.

Please also check out our next blog post in this Twiliog series that shows you how to send a confirmation email using Twilio SendGrid when the user successfully registers!

About Authors

Yi Tang is a Senior Solutions Engineer at Twilio. She loves helping Twilio's customers achieve their business goals and is always looking for ways to improve and optimize their use of Twilio products. She can be found at ytang [at] twilio.com

Pooja Srinath is a Principal Solutions Engineer at Twilio. She's focused on learning new software technologies that help create solutions, address use cases, and build apps to solve some of the industry's most challenging requirements. Pooja uses Twilio's powerful communication tools & APIs to build these ideas. She can be found at psrinath [at] twilio.com.