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:
- The user submits a phone number to the application.
- The phone number is verified as real, or not; and whether the phone is a mobile or a landline.
- If the number is a valid cell, the user is sent an authentication code.
- 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:
bash
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:
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:
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.
<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.