If you ever need to contact your application's users by phone then it's good practice to verify that the phone number they enter is in fact theirs. Let's see how to verify phone numbers in a Rails 5 application using the Authy Phone Verification API.
What you'll need
To code along with this post, you'll need:
- A Twilio account
- An Authy App which you can create in the Twilio console, you'll need the API key
- Ruby and Rails installed, I am using the latest Rails 5.1.2 and Ruby 2.4.1
Getting started
Create a new Rails application for this project:
rails new phone_verification
cd phone_verification
We need to add a couple of gems that we'll use in the project, open up the Gemfile
and add authy
and envyable
. Authy is going to be used for the phone verification, envyable to manage environment variables.
gem 'envyable'
gem 'authy'
Install the gems, then initialise envyable from the command line:
bundle install
bundle exec envyable install
Open up config/env.yml
and add your Authy app API key:
AUTHY_API_KEY: YOUR_API_KEY_HERE
Now, initialise Authy itself by creating the file config/initializers/authy.rb
and add the following code:
Authy.api_key = ENV['AUTHY_API_KEY']
Authy.api_uri = 'https://api.authy.com/'
That is all the initialisation we need, we can now get on with building the application.
Building the app
The process of verification is quite straightforward.
- The user enters three bits of information
- Phone number
- Country code
- Whether they want to verify by voice or SMS
- Our application makes a request to the API to initiate the verification
- The user receives a call or text message with a 4 digit code
- They then enter that code into a form in the application
- The code is sent off to the API, with their phone number and country code again, to verify
- If it is correct then the user has verified their number
We're going to need a controller that will both present the views we need as well as do the work to initialise the verification and actually verify the phone number. Generate a controller with:
bundle exec rails generate controller phone_verifications new create challenge verify success
This will create five actions and five views. We don't need the create and verify views, so you can delete those. It will also generate some routes. Open config/routes.rb
remove the 5 new routes and replace them with:
resources :phone_verifications, :only => [:new, :create] do |p|
collection do
get 'challenge'
post 'verify'
get 'success'
end
end
This creates 5 routes which you can check out by running rails routes
on the command line. They look like this:
bundle exec rails routes
Prefix Verb URI Pattern Controller#Action
challenge_phone_verifications GET /phone_verifications/challenge(.:format) phone_verifications#challenge
verify_phone_verifications POST /phone_verifications/verify(.:format) phone_verifications#verify
success_phone_verifications GET /phone_verifications/success(.:format) phone_verifications#success
phone_verifications POST /phone_verifications(.:format) phone_verifications#create
new_phone_verification GET /phone_verifications/new(.:format) phone_verifications#new
The first view
The new
action needs to display a form to the user that takes their phone number, the country code of the country they are in and whether they want the verification by SMS or voice call.
To generate the list of country codes and make an interface that we can use to search by country, include the Authy JavaScript form helpers. Add to the <head>
of app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'https://cdnjs.cloudflare.com/ajax/libs/authy-form-helpers/2.3/form.authy.min.css', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/authy-form-helpers/2.3/form.authy.min.js', 'data-turbolinks-track': 'reload' %>
Now, in app/views/phone_verifications/new.html.erb
replace the code that was generated with the following:
<h1>Verify your phone number</h1>
<%= form_tag(phone_verifications_path, method: :post) do -%>
<% if @response %>
<% @response.errors.each do |key, message| %>
<p><%= message %></p>
<% end %>
<% end %>
<div>
<%= label_tag "authy-countries", "Country code:" %>
<%= select_tag "authy-countries", nil, name: 'country_code', 'data-show-as': 'number' %>
</div>
<div>
<%= label_tag "phone_number", "Phone number:" %>
<%= telephone_field_tag "phone_number", nil, name: 'phone_number' %>
</div>
<div>
<p>Verification method</p>
<%= label_tag "method_sms", "SMS: " %>
<%= radio_button_tag "method", "sms" %>
<%= label_tag "method_call", "Call: " %>
<%= radio_button_tag "method", "call" %>
</div>
<%= button_tag "Verify" %>
<% end -%>
This code sets up the form to submit the three fields. Using the ID "authy-countries"
means that the select_tag
will be picked up by the Authy JavaScript and augmented to become a searchable list of countries.
We also use telephone_field_tag
to create an HTML input with a type of "tel"
so that mobile devices show an optimised keyboard for entering telephone numbers.
Start up the server:
bundle exec rails server
Navigate to http://localhost:3000/phone_verifications/new to see the new form and try out the country code field.
The verification process
Now we have our form to start the process, we need to begin the verification. Using the parameters entered into the form, we will build an API request to the Authy phone verification API and send it off. If it is successful we will present the form asking for the verification code, otherwise we should re-render the initial form and show the error.
In app/controllers/phone_verifications_controller.rb
replace the empty create
method with this code:
def create
session[:phone_number] = params[:phone_number]
session[:country_code] = params[:country_code]
@response = Authy::PhoneVerification.start(
via: params[:method],
country_code: params[:country_code],
phone_number: params[:phone_number]
)
if @response.ok?
redirect_to challenge_phone_verifications_path
else
render :new
end
end
The key part here is the call to Authy::PhoneVerification.start
. We pass the parameters we gathered from the user, including the way to contact them, the phone number and the country code. If the response to this is ok, then the verification process will start, otherwise there is an issue.
We already built in the code to display errors if something does go wrong. These lines in the view handle that:
<% if @response %>
<% @response.errors.each do |key, message| %>
<p><%= message %></p>
<% end %>
<% end %>
Note too, that we save the phone number and country code to the session so that we can use them later. In a full application these would be saved against a user or phone number record in the database and we'd keep the ID of the record in the session.
The challenge
We now need users to enter the 4 digits that the verification process sends them. Add the following code to app/views/phone_verifications/challenge.html.erb
:
<h1>Challenge</h1>
<%= form_tag(verify_phone_verifications_path, method: :post) do -%>
<% if @response %>
<% @response.errors.each do |key, message| %>
<p><%= message %></p>
<% end %>
<% end %>
<div>
<%= label_tag "code", "Enter the code you were sent:" %>
<%= text_field_tag "code" %>
</div>
<%= button_tag "Verify" %>
<% end -%>
Time to verify
Now we have the user's 4 digit code we need to verify it with the API. Replace the empty verify
action in the PhoneVerificationsController
with:
def verify
@response = Authy::PhoneVerification.check(
verification_code: params[:code],
country_code: session[:country_code],
phone_number: session[:phone_number]
)
if @response.ok?
session[:phone_number] = nil
session[:country_code] = nil
redirect_to success_phone_verifications_path
else
render :challenge
end
end
This time the key part here is making the request to Authy::PhoneVerification.check
passing in the phone number and country code that we saved earlier along with the code the user entered. If the response is ok this time, then the verification was a success and we can clear the session and redirect to the success path. In a real application you would likely mark that the user or phone record was indeed verified and save that in the database too.
If the code was wrong, we'll just show the challenge view again so that the user can try again if they made a mistake.
To finish off, add a success message to app/views/phone_verifications/success.html.erb
:
<h1>Congratulations!</h1>
<p>You have successfully verified your phone number.</p>
Let's test it out
If you didn't start the server earlier, do so now:
bundle exec rails server
Go to http://localhost:3000/phone_verifications/new and enter the correct details. When you get the phone call or SMS message, enter the verification code into the second form and you will see a message of success that your phone is verified.
Phone numbers? Verified!
That's how to get started with the Authy Phone Verification API in Rails 5. You can check out the full code for this article on GitHub. This application is far from complete of course, there is much more to do. You'd want to:
- save the phone number and its verification status to a user model in your database
- handle errors better, giving users the chance to resend or call again or check and update the phone number
If you've got any questions or comments, then please do get in touch. Leave me a comment below or give me a shout on Twitter.
Verify user phone numbers in Ruby on Rails with the Authy Phone Verification API was originally published on the Twilio blog on August 30, 2017.
Top comments (2)
Hey Rajan,
Of course, that is a good idea for verifying that users are real people before sign up. For that I would follow the process from this blog post, but when it gets to the verification stage set a session cookie that says the new user is verified successfully. Then you can redirect to your registration flow.
The registration flow should redirect to the verification flow if the cookie is not set (indicating that the user hasn't yet verified their phone number).
That is likely where I would start. Although, I would also note that this blog post covers the version 1 API for Verify. There is a beta version of Verify version 2 available, which I might consider building with.