Verifying Phone Numbers in Ruby on Rails with Twilio's Verify API

September 07, 2018
Written by
Daniel Phillips
Contributor
Opinions expressed by Twilio contributors are their own

6htvpXAFaN9DGB25bk0nOoDQcJqvbUhjC8-aRXWa6H9pBeJFdyh5V0hhzVKTYiIBmkPW976rAK-nOt_KZvceefMvI1GMonwOQvmXJ5h9raCNYEk0u8K0Zf7JTI4LIbG40iFaDzMp

In the public-facing world of apps, verifying that users are real people can be tough. This is where phone verification really becomes an asset, helping to mitigate fraud.

In this post, we will walk through integrating Twilio’s Verify API into a Ruby on Rails application to discover whether a user’s phone number exists, its type of line, and its carrier. We will then authenticate the user with Verify’s token verification.  

Getting Started

We will build a simple login action, with phone verification. This walk-through will provide basic functionality that can be ported to new or existing Ruby/Rails applications.

At a high level, successful interactions will look something like:

  1. The user submits a phone number to the application.
  2. The phone number is verified as real, or not; and whether the phone is a mobile or a landline.
  3. If the number is a valid cell, the user is sent an authentication code.
  4. The user enters the code to complete the authentication process.

Of course, a lot can go wrong in this process, so we’ll have to plan to handle exceptions to this process as we build this service.

Note: completed code for this walkthrough is available here.

Environment Setup

We’ll be using Ruby 2.5.0, best installed and managed using either rvm or rbenv, and we will use PostgreSQL for storage. If you’re on a Mac, I’d recommend using homebrew for installing these; otherwise, see install instructions for your environment in the documentation for each service. You'll need the Rails gem installed as well, which you can do with:

gem install rails

Which also gives us Bundler, which we’ll use for package management.

Then, start a new Rails application with:

rails new bot_or_not -d postgresql
cd bot_or_not

For this project, we will need to install the Authy gem. This library allows us to interact with the Twilio Verify API.

So, in your Gemfile, add:

gem 'authy'

Let’s also add Pry to our Gemfile, for great Ruby debugging:

In the group :development block (we don’t accidentally want debugger in prod!), add

gem 'pry-rails' 

Then, on the command line, run:

bundle install

Lastly, let’s create our development and test databases:

rails db:create

Building the Login Service        

To have a functioning login page, we need to implement the ability to CRUD. a resource, which in our case, is a user.

We could build this ability by hand, but, for the purposes of this article, why not take advantage of Rails’ out-of-the-box ability to do this for us? Rails Generators can build out an entire CRUD-able resource for us, adhering to RESTful routing and MVC design (lots of buzzwords here—feel free to to follow the links to read more :) ).

From the command line:

rails generate scaffold User name:text country_code:string phone_number:text

If you’re not familiar with Rails, just know that this does a lot for us. First, it creates a migration to make a users table with name and phone_number columns. It then creates a corresponding user model. It makes all RESTful routes for the user resource, controller actions for each route, and, finally, very simple stock Rails views for the user resource. To verify, try:

rake routes

And you should see:

Prefix       Verb       URI Pattern                  Controller#Action
users        GET        /users(.:format)             users#index
             POST       /users(.:format)             users#create
new_user     GET        /users/new(.:format)         users#new
edit_user    GET        /users/:id/edit(.:format)    users#edit
user         GET        /users/:id(.:format)         users#show
             PATCH      /users/:id(.:format)         users#update
             PUT        /users/:id(.:format)         users#update
             DELETE     /users/:id(.:format)         users#destroy

Note: If you are using Rails 5.2, you may see more routes related to Active Storage, but we don’t need to worry about those right now for our purposes.

Then, let’s run our migration to create our users table, and fire up our dev server:

 

rails db:migrate && rails s

According to the routes we see above, if we open a browser and navigate to localhost:3000/users/new, we should see:

Rails Magic!! 👏👏

Go ahead and create a user and submit. After doing so, you should see:

Great! Now we know that we can create and save users and phone numbers.

Registering an App in the Twilio Console

We will need some credentials to interact with the Authy API. To access the Twilio Developer Console, head to https://www.twilio.com/console/verify/applications. If you don’t have an account, feel free to sign up on the free tier, which will be sufficient for what we’re building here.

Once logged in, select ‘Create New Application’ to get started. After giving the app a name, you should see:

Twilio Console with Verify settings

The details of this page help for configuring exactly how users interact with the application. For now, what we really need is the API key to get some requests working. Let grab that API key:

Copy the key to your clipboard, and let’s head back to the console.

Try:

curl -XPOST 'https://api.authy.com/protected/json/phones/verification/start' -H "X-Authy-API-Key: <YOUR_KEY>" -d via='sms' -d phone_number=<VALID_CELL_NUMBER> -d country_code=<VALID_COUNTRY_CODE>

Be sure to replace <YOUR_KEY> with your API copied from the console. Regarding the  <VALID_CELL_NUMBER>,  I’d recommend using your own cell number for this so that strangers don’t receive invitations to authenticate on your app.

And, boom! You should have received an object in response, including various information about the cell phone and any actions taken. Also, using your cell, you should receive a text message.

Using Authy-Ruby

We can now make successful requests for information about and validity of our cell phone numbers. The next step is to do this programmatically in our app.

Our Authy gem gives us access to a Ruby library that abstracts Twilio’s Authy API. There’s quite a bit of functionality built in, but we are particularly interested in the PhoneVerification class, with its start and check methods.

According to the docs, start begins the process by verifying a user’s cell phone information and that it has SMS capability, and upon receipt of those two things, the API sends a text message with an expiring code to the user. Let’s try this in the Rails console:

rails c

Here, we can go ahead and try out the Authy library:

pry(main)> Authy::PhoneVerification.start(country_code: <YOUR_COUNTRY_CODE>, phone_number: '<YOUR_CELL>')

=> {"error_code"=>"60001", "message"=>"Invalid API key", "errors"=>{"message"=>"Invalid API key"}, "success"=>false}

Oops! The library doesn’t know the API key to use. In your config/initializers directory, add authy.rb file, and in it:

Authy.api_key = 'your-api-key'
Authy.api_url  = 'https://api.authy.com'

Note: to keep your api keys private, you should add this file to your .gitignore file if you plan on pushing this to project to a public repository.

Now restart the Rails console, and let’s try again. Using a valid phone number, you should see a response like:

pry(main)> Authy::PhoneVerification.start(country_code: <YOUR_COUNTRY_CODE>, phone_number: '<YOUR_CELL>')
=> {"carrier"=>"Verizon Wireless", "is_cellphone"=>true, "message"=>"Text message sent to +1 XXX-XXX-XXXX.", "seconds_to_expire"=>599, "uuid"=>"xxxxxxxxxxxxxxxxx", "success"=>true}

Also, the phone number given should have received something like:

The code given is valid for 10 minutes, so if you’re still within that window, we can try the check method with the same code (don’t worry if it’s expired, you should just be able to send the start method again):


Authy::PhoneVerification.check(verification_code: '<CODE>', country_code: ‘<YOUR_COUNTRY_CODE>’ , phone_number: '<SAME_NUMBER_FROM_FIRST_REQUEST>')

=> {"message"=>"Verification code is correct.", "success"=>true}

Great, we’ve spiked out the verify feature using the library. Now let’s get this functionality into our app.

To keep responsibilities separated, let’s add a new /services/ directory in the ./app/ directory, and in it, a verify.rb file. Because the nature of this service will be functional and won’t need to handle state, let’s make this a Ruby module.

module Verify
end 

In it, let’s start with a method for determining whether the phone number is valid, like we did in our console:

module Verify
  def valid_phone_number?(country_code, phone_number)
    response = Authy::PhoneVerification.start(country_code: country_code, phone_number: phone_number)
    response.success?
  end
end

This way the method will act as guard, returning a boolean that we can easily use for controlling the flow to our user_controller. To have the method available to us in our controller, be sure to include it in our UserController class.


class UsersController < ApplicationController
  include Verify

  # rest of the controller
end

At the bottom of the file, the scaffolding gave us a private method called user_params.

    def user_params
      params.require(:user).permit(:name, :country_code, :phone_number)
    end

When invoked, this method creates a hash of only those attributes passed in to the permit method. This will be helpful when we need to use the inputted country_code and phone_number for verification.

You’ll notice that a lot came out of the box here when we scaffolded the user resource, some of which we don’t need. Thinking back to our high-level interactions, this is where we can tackle the first two:

  • User submits a phone number to the application.
  • The phone number is verified as real, or not; phone line type is determined.

Let’s do some work to our create method. Get rid of the scaffolded code that came in the create method and replace it with:


def create
@user = User.new(user_params)
    if valid_phone_number?(user_params['country_code'], user_params['phone_number'])
      @user.save
      redirect_to @user, notice: 'You have a valid phone number!'
    else
      flash.alert = 'Please enter a valid phone number'
      render :new
    end
end

Notice we used an alert for the else condition. To make sure this render properly, go to /app/view/users/new.html.erb and change:

<p id="notice"><%= notice %></p>

to:  

<p id="alert"><%= alert %></p>

The above additions and changes will do a few things for us: the initial if statement will evaluate our valid_phone_number? method as true or false. If successful, we will persist the user’s name and phone number to our user table; otherwise, with an unverified number, the else condition is run and the user is redirected back to the initial form.

Let’s try it!

Spin up our local Rails server with rails s and navigate back to localhost:3000/users/new.

You should see the same form, but now, it should behave differently. Entering a nonsense number will cause the guard method we added above to return false, returning us to this same form. Entering a valid number should create the user and send an SMS like we saw above.

We still need to implement our check method. This should happen _only after_ saving a user with a valid phone number since a valid code is only texted to a user with a valid number.

We can deal with this by adding an input to the user#show path. This approach fits nicely into our applications flow, since a user is only delivered to this resource if it is created, and it is only created if the user provides a valid cell number.

In our views, then, add a simple Rails form_tag to the user/show.html.erb

<h3>Verify your number</h3>
<%= form_tag @user, url: user_path(@user.id), :method => :put do %>
  <%= label_tag :code, "Enter the code you received at #{@user.phone_number} here" %>
  <%= text_field_tag :code %>
  <%= submit_tag 'Verfiy' %>
<% end %>

Which, in the context of what was already there from the scaffolding, should look something like:

0D6MKVx9ooPmnKKriaQ0wgx97DsBPkgOMNk6btXGxi7w2lPRp0NdaXN0LaGATX8X9A7dLNl-8_r9oFSzG2oXXrvzti17aYNl4Fi_HA_7izZGUuGfbUrFKzSEcXBoURh59NSc0uA6

You’ll notice in our form_tag that we specified the verb and resource, so this action will take us to the update action in the user controller.

At this point, we need to add a `check` method to our module. The interface to this method will need to identify the user's phone number and check the code received by the user against it. Again, this is a good opportunity for a guard method that returns a boolean. So, in the Verify module, add the following:

  def valid_confirmation_code?(code, country_code, phone_number)
    response = Authy::PhoneVerification.check(verification_code: code, country_code: country_code, phone_number: phone_number)
    response.success?
  end

Now we can check the code against our local persisted user information and check both against the Authy API to complete verification. Also, we might want to indicate in our backend that the user has been verified. It’s easy enough to add this to the user table:

rails g migration AddVerifiedToUser

And in the generated migration file:

  def change
    add_column :users, :verified, :boolean, default: false
  end

Then, back on the command line

rails db:migrate

With this in place, let’s add an indicator on the users show page, along with a guard only showing the form if the user is not verified.

2 3 4 5 6 13"
<p>
<strong>Verified:</strong>
<%= @user.verified %>
</p>

<% if !@user.verified %>
  <h3>Verify your number</h3>
  <%= form_tag @user, url: user_path(@user.id), :method => :put do %>
  <%= label_tag :code, "Enter the code you received at #{@user.phone_number} here" %>
  <%= text_field_tag :code %>
  <%= submit_tag 'Verify' %>
  <% end %>
<% end %>

So, porting this functionality now to our controller, take a minute to rearrange the update action. First, since we are fundamentally changing the update action, we don’t want to allow for edit ability that our scaffolding gave us. So, let’s get rid of anything that would lead us back to the update action, except for the phone verification flow:

In `/app/views/users/show.html.erb` and in `/app/views/users/index.html.erb` delete

<%= link_to 'Edit', edit_user_path(@user) %>

Then, in our update action:

  def update
    if  valid_confirmation_code?(params['code'], @user.country_code, @user.phone_number)
      @user.update(verified: true)
      redirect_to users_path, notice: "#{@user.phone_number} has been verified!"
    else
      redirect_to @user, alert: 'invalid or expired token'
    end
  end

Here, we’ve handled the update action in a similar fashion to the create action in terms of flow control. The key difference is that on successful return of the valid confirmation code, we update the verification column on the user.

To ensure that our success notice or error alert are displayed on the page we need to add one more thing to the view. Open `app/views/users/show.html.erb` and add the alert message below the notice.


<p id="notice"><%= notice %></p>
<p id="alert"><%= alert %></p>

And with that, we’ve handled all of the logic for our authentication process!  👏👏

Next Steps

This is a simple implementation of using Verify for phone numbers. To dig a little deeper, I would suggest looking into other verification methods offered by the Authy gem, and experimenting with Twilio’s other tools for preventing fraud, specifically, the Lookup API.

Thanks for following along, I'm Daniel Phillips and you can find me on the following sites: @d_philla and github.com/dphilla.