Using Twilio for User Verification on your website
Meredith Hassett
Posted on August 5, 2020
While I love Twilio for sending text messages as much as the next person, I haven't spent much time digging into the rest of their offerings. And there is so much to explore! Today, I want to look at how to use the Verify API from Twilio to require that a user verifies themselves before moving forward in a process. This can used for 2FA on accounts or verifying purchases from anonymous users.
A breakdown of how this will all work:
- we need to capture the user's phone number
- start a verification process with Twilio
- have the user enter their code
- retrieve that code and pass it to Twilio's verification check service
- get the green light and go!
So we need at least 2 UI inputs and 3 calls to Twilio. WAIITT...where did 3 come from, you only outlined 2. Well to make sure a phone number is in the proper format, instead of doing the regex verification on our side, Twilio has a service we can use for that too!
The first thing that you will need is an input field with the phone number. This can be on your sign-up screen, payment details, account settings page, wherever makes sense for your app. Since I'll be using Corvid by Wix, I am just going to drag and drop a Text Input UI element onto my page and give it an ID of "phoneInput". You may also want a button to trigger the Verification process, so feel free to add a button with id "verifyButton".
Once we have our UI elements on our page, we need to add an event to the button click to retrieve the value of the phone number and pass it along to the Twilio Verify service. I am going to do the verification process in the backend of my app so I'll start by importing my backend function from my Twilio file.
import { sendVerification } from 'backend/twilio'
Next, in my page onReady event, I will add a new event listener for my button.
$w('#verifyButton').onClick(async () => {
})
In my event listener, I will GET the value of the phone number input and assign it to a new variable.
$w('#verifyButton').onClick(async () => {
let userPhone = $w('phoneInput').value
})
And lastly (for now), we'll pass this phone number as a parameter to our backend verification function.
$w('#verifyButton').onClick(async () => {
let userPhone = $w('phoneInput').value
sendVerification(userPhone)
})
Now we actually need to implement this backend function, so switch over to your backend code. In Corvid, we can accomplish this by going in our file structure to our Backend folder, and either add a new file or work with an existing one. Since we're still working with the Twilio npm module, I will add these functions to my existing twilio.jsw file.
Let's stub out the new sendVerification
function. We need it to accept 1 parameter for the user entered phone number.
export async function sendVerification(phoneNumber) {
}
When using functions beyond the file they live in, make sure to export them so other code files can import their functionality.
Inside this verification function, we need to instantiate a new Twilio client, so we'll need our Account SID and Auth Token like we did for sending a text message. If you don't remember how we set this up, take a look at this blog post on working with Twilio and Corvid.
export async function sendVerification(phoneNumber) {
const authToken = await wixSecretsBackend.getSecret("twilio_auth_token")
const accountSid = await wixSecretsBackend.getSecret("twilio_account_sid")
let twilioClient = new twilio(accountSid, authToken)
}
For the Twilio Verify API, we all need to create a new service. I did so via my Twilio account, but you can also use the API to create a new service. Once you create the service, take note of its ID as we will need it throughout the application. Since I created my service via my account GUI, I added it in the secrets manager for my application.
const verifyId = await wixSecretsBackend.getSecret("twilio_verify_id")
Now we have everything we need to start a verification process!
The Verification API is picky about the phone number format, so we can use Twilio's Lookup API to find the proper phone number format every time no matter how the user enters it. Let's start with looking up the phone number. We'll pass in the user entered phone number to the phoneNumbers
lookup function.
twilioClient.lookups.phoneNumbers(phoneNumber).fetch()
Once this promise resolves, we'll have the number in the proper format we need via the returned object. On the promise resolve success, let's move forward with the Verify API's Verification Service. We need to use the service we created, so this is where you need the Service ID. From the services, we want to use the Verification service and create a new verification. A verficaition service needs 2 inputs via a JSON object: the phone number to send the code to and which channel to use (ie Call or SMS). I am going to set it to SMS.
twilioClient.verify.services(verifyId).verifications.create({ to: <returnedPhoneNumber>, channel: 'sms' }))
Now we know we need to chain this together as we need the number from the lookup API to run the verification endpoint, so all linked up, it would look something like this:
twilioClient.lookups.phoneNumbers(phoneNumber).fetch()
.then(phone_number => twilioClient.verify.services(verifyId).verifications.create({ to: phone_number.phoneNumber, channel: 'sms' }))
The last thing we want to do is make sure to return the results so our calling code knows what to do next. For example, in our UI if we get a positive response from the verification service, we want to open up a new Input field for the user to enter their code But if the response was negative, maybe we need the user to reenter the phone number or wait for the service to come back online. So to finish up our backend code, make sure to return the result from your Verify API call via another chained then()
and finally return the whole result to the calling function.
return twilioClient.lookups.phoneNumbers(phoneNumber).fetch()
.then(phone_number => twilioClient.verify.services(verifyId).verifications.create({ to: phone_number.phoneNumber, channel: 'sms' }))
.then(verfication => verfication)
Sweet! Now we need to do something with this verification result on our UI, so jump back to your frontend where you have the user input their phone number.
For me, I am going to use a lightbox to collect the user's code they received on their mobile device. I have created a lightbox called "Verify". You can show a new input field on the same screen or also use a lightbox. Whatever fits your need!
When sendVerification()
returns, I want to check the result and determine if I should open the lightbox. I'll use an if()
statement to look at the result object's status.
sendVerification(userPhone).then((result) => {
if (result.status === 'pending') {
}
})
If the code was successfully sent, we'll see a status of "Pending". When we see that status, I am going to open my lightbox using the Wix Window API. Additionally, I'll need to phone number of the user again to check to make sure they are using the correct code sent to that number, so I'll also make a data object to send along to the lightbox.
if (result) {
let userData = {
"number": userPhone
}
wixWindow.openLightbox("Verify", userData)
}
In the lightbox, I can retrieve my passed in data by retrieving the context of the lightbox also via the Wix Window API. Make sure to import this API on all pages and lightboxes that are using it!
const context = wixWindow.lightbox.getContext()
This lightbox contains my next Text Input field called "codeInput" that is where the user should enter their code they received on their mobile device. I also have a button called "verifyButton" to kick off the Verification Check process. This process will also being running in my Twilio backend folder, so make sure to import you Verification Check process function at the top of your code.
import { verifyUser } from 'backend/twilio'
When my Verify button is clicked, I want to start my verifyUser()
function, which will take in the user's phone number and the entered code. Let's attach an onClick()
event to the button.
$w('#verifyButton').onClick((event) => {
})
In this onClick
event, call the verifyUser()
function, and pass in the phone number and code to verify.
$w('#verifyButton').onClick((event) => {
let code = $w('#codeInput').value
verifyUser(context.number, code)
})
And now we need to build out the verifyUser()
function in our Twilio backend code! Switch over to your Twilio backend code file.
Here's let stub out this new function.
export async function verifyUser(number, userEnteredCode) {
}
We need a Twilio Client again, plus the Service ID from the service we created earlier. Grab these values from your Secrets Manager, or wherever else you stored them. Then initialize your Twilio Client.
export async function verifyUser(number, userEnteredCode) {
const authToken = await wixSecretsBackend.getSecret("twilio_auth_token")
const accountSid = await wixSecretsBackend.getSecret("twilio_account_sid")
const verifyId = await wixSecretsBackend.getSecret("twilio_verify_id")
let twilioClient = new twilio(accountSid, authToken)
}
We also need to make sure the user's phone number is properly formatted again, so use the Twilio Client and the Lookups API we used earlier to set this up.
twilioClient.lookups.phoneNumbers(number).fetch()
If you wanted to avoid making this call again, you can also retrieve the to
property from the return verification object from above and use that as the user phone data object to keep the properly formatted number.
Once we have our phone number ready to go, we can access the Twilio Verify API again and this time use the Verification Checks service. This service takes in the phone number and code and makes sure it matches what Twilio recorded sending.
twilioClient.verify.services(verifyId)
.verificationChecks
.create({ to: <userEnteredPhoneNumber>, code: userEnteredCode })
If you are using the Lookups API, you will need to chain your promises to look something like this:
twilioClient.lookups.phoneNumbers(number).fetch()
.then((phone_number) => {
return twilioClient.verify.services(verifyId)
.verificationChecks
.create({ to: phone_number.phoneNumber, code: userEnteredCode })
})
And lastly, we need to make sure to return the result to the calling function. If the result does not say approved, the user may need to reenter the code or get a new code as it expires after 10 minutes.
return twilioClient.lookups.phoneNumbers(number).fetch()
.then((phone_number) => {
return twilioClient.verify.services(verifyId)
.verificationChecks
.create({ to: phone_number.phoneNumber, code: userEnteredCode })
}).then((result) => {
if (result.status === 'approved') {
return "verified"
} else {
return "NOPE"
}
})
And back on the UI, we can add in some additional error handling based on the result.
verifyUser(context.number, code).then((result) => {
if(result === 'verified'){
wixWindow.lightbox.close({"status":"success"})
} else {
$w('#errorText').text = "Code No Bueno :( Try Again"
$w('#errorText').show()
}
})
TADA! Verification Complete!
This is just a sample of what you can do with the Twilio Verify API and just one way of flowing it into your application. Feel free to play around with this to fit your applications needs, but alway remember with the Twilio Verify API you need to do these steps in order:
- Create a Twilio Service
- Create a new Verification
- Check the Verification with the same information as you created it with
Enjoy!
Posted on August 5, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.