How to build SMS Phone Verification in Rails 4 using AJAX

June 18, 2015
Written by

sms-verification-featured-image

You’ve undoubtedly done something like this before:

airbnb-sms-verification

That’s the SMS phone verification for AirBnB, which uses Twilio to allow hosts and guests to chat without sharing their actual phone numbers. In this tutorial, we’ll use Twilio SMS and AJAX to add phone verification into a Rails 4 app.

Warning: There are a bunch of great reasons to grab a user’s phone number, but if your end goal is two-factor authentication, you should close this tutorial right now and use Authy instead. 2FA is a tricky beast, wrought with edge cases and security concerns — it’s typically a bad idea to roll your own.

However, if you’re looking to verify phone numbers in your Rails app for some other reason, by all means press on.

Create the Model

First we need a Rails project (we’ll use a fresh one for the purposes of this tutorial, but it shouldn’t be too hard to replicate these steps to integrate SMS verification into your preexisting Rails 4 app):

rails new sms-verification
cd sms-verification

Add the Twilio gem to your Gemfile :

gem 'twilio-ruby', '~> 4.1.0'

Install your gems:

bundle install

Create a phone_number  model to store:

  • the phone number itself
  • a randomly generated PIN
  • A flag indicating if the number has been verified

rails g model phone_number phone_number:string pin:string verified:boolean

Create the database and run the migration:

rake db:create db:migrate

Now we have the M in MVC. Let’s do the C.

Create the Controller

As mentioned before, our verification process has three steps:

  1. The user enters a new phone number
  2. Our app creates the phone number row and sends the user a PIN to enter back into the form
  3. Our app verifies that the PINs match for the given phone number.

Add this code to config/routes.rb to create a route for each step:

resources :phone_numbers, only: [:new, :create]
post 'phone_numbers/verify' => "phone_numbers#verify"

Create the phone_numbers controller we just referenced in those routes:

rails g controller phone_numbers

Inside the controller (found at  app/controllers/phone_numbers_controller.rb), add a straightforward new  action that creates a  @phone_number  that we’ll use in a  form_for block in the view:

def new
  @phone_number = PhoneNumber.new
end

The beginnings of our C are in place. On to the V.

Create the View

From the user’s perspective, SMS verification involves three steps:

  • Enter a New Phone Number
  • Verify the PIN
  • Receive a success or failure message

To avoid jarring page loads, we’ll use AJAX to complete the process on a single page like we saw in AirBnB’s example.

But before we create the phone verification form, let’s address a slight problem: default Rails views are hideous. We can remedy that by simply including an externally hosted Bootstrap stylesheet.

In app/views/layouts/application.html.erb, add this line above where the application stylesheet is included:

<%= stylesheet_link_tag "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css", media: 'all', 'data-turbolinks-track' => true %>

Then replace that files current  <body> with this:

<body> 
  <div class="container">
    <%= yield %> 
  </div>
</body>

Now that our page won’t make our eyes bleed, we can create a new phone number view:

touch app/views/phone_numbers/new.html.erb

Add this code to that file to create a form for Step 1:

<div id="send-pin">
  <h3>What's your phone number?</h3>
  <%= form_for @phone_number, remote: true do |f| %>
    <div class="form-group">
      <%= f.text_field :phone_number %>
    </div>
    <%= f.submit "Send PIN", class: "btn btn-primary", id: 'send-pin-link' %>
  <% end %>
</div>

Make sure everything looks okay. Start your Rails server:

rails s

Visit localhost:3000/phone_numbers/new in a browser and check out your form. Should look something like this:

sms-verification-step-1

Before we wire up that “Send PIN” button, add this to the same file to compete the view:

<div id="verify-pin">
<h3>Enter your PIN</h3>
<%= form_tag phone_numbers_verify_path, remote: true do |f| %>
  <%= hidden_field_tag 'hidden_phone_number', '' %>
  <div class="form-group">
    <%= text_field_tag :pin %>
  </div>
  <%= submit_tag "Verify PIN", class: "btn btn-primary" %>
  <% end %>
</div>

<div id="status-box" class="alert alert-success">
  <p id="status-message">Status: Haven’t done anything yet</p>
</div>

This creates a form with:

  • A hidden field to store the phone number (we will update this field using AJAX after the user completes Step 1).
  • A text field for entering the PIN
  • A submit button

It also adds a status-message  where we’ll display a message after we attempt to verify the PIN. Refresh your page and make sure you can see the elements.

sms-verification-all-steps

But wait! We don’t need to see Steps 2 and 3 yet!

Add this CSS to app/assets/stylesheets/phone_numbers.scss  (Rails auto-created that file for you) to make them disappear when the page loads. We’ll reveal them at the appropriate time with AJAX:

#verify-pin, #status-box {
  display: none;
}

Refresh the page and ensure that you’re back to only seeing Step 1. With our finished view, we’ll head back to the controller and model to make it work.

Generate a PIN and send it via SMS

For Step 2, we need a create action in our controller that will do four things:

  • Create the phone number entered into the form (or find it if it already exists)
  • Generate a PIN
  • Send the PIN via SMS
  • Return JavaScript to reveal and prepare Step 2 of the view

Add this action to  PhoneNumbersController inside  phone_numbers_controller.rb :

def create
  @phone_number = PhoneNumber.find_or_create_by(phone_number: params[:phone_number][:phone_number])
  @phone_number.generate_pin
  @phone_number.send_pin
  respond_to do |format|
    format.js # render app/views/phone_numbers/create.js.erb
  end
end

The astute reader will notice that we used two methods on @phone_number that don’t exist yet exist.

Add this method inside your PhoneNumber  class in app/models/phone_number.rb to generate a four digit PIN:

def generate_pin
  self.pin = rand(0000..9999).to_s.rjust(4, "0")
  save
end

This method, called by the controller when a phone number is created, generates a PIN and saves the record.

Before we can send this PIN via SMS, we need three things from Twilio:

  • Your account SID
  • Your auth Token
  • An SMS enabled phone number

Go to your Twilio account dashboard and find your account SID and auth token:

account-sid-and-auth-token

Also head over to your phone numbers list and find one you want to use for this app.

We’ll set these values as environment variables along with your Twilio phone number. Phil Nash wrote a great post on the multitude of ways to set environment variables in Ruby, but for the sake of this tutorial, we’ll do it in the simplest way possible. Kill your server, then punch this into the same terminal window:

export TWILIO_ACCOUNT_SID="REPLACEWITHACCOUNTSID"
export TWILIO_AUTH_TOKEN="REPLACEWITHAUTHTOKEN"
export TWILIO_PHONE_NUMBER="REPLACEWITHTWILIONUMBER"

Add two methods to our PhoneNumber  class to send an SMS.

This method creates a Twilio REST client:

def twilio_client
  Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])
end

This method sends the PIN via SMS:

def send_pin
  twilio_client.messages.create(
    to: phone_number,
    from: ENV['TWILIO_PHONE_NUMBER'],
    body: "Your PIN is #{pin}"
  )
end

Last thing our create action does is to return JavaScript that will:

  • Add the phone number to our hidden field
  • Reveal Step 1 and hide Step 2
  • Move the focus onto the PIN input

Create a new file:

touch app/views/phone_numbers/create.js.erb

Add this to that file:

$('#hidden_phone_number').val('<%= @phone_number.phone_number %>' )
$('#send-pin').hide()
$('#verify-pin').fadeToggle()
$('#pin').focus()

And that’s it for create! Restart your server, reload your page and give it a try!

Verify the PIN

The third and final step in our phone verification is the easiest: we check to see if the entered PIN matches the generated PIN. If so, we update the verified flag on the phone number record and update the page with a happy message. Otherwise, sad message.

Add this  verify action to the PhoneNumbersController :

def verify
  @phone_number = PhoneNumber.find_by(phone_number: params[:hidden_phone_number])
  @phone_number.verify(params[:pin])
  respond_to do |format|
    format.js
  end
end

You’ll again notice that  @phone_number.verify isn’t a thing yet. Add this to your  PhoneNumber model:

def verify(entered_pin)
  update(verified: true) if self.pin == entered_pin
end

Finally, let’s write our JavaScript to update the status message in the view. Create a new file:

touch app/views/phone_numbers/verify.js.erb

In that file, paste in this code that will display the appropriate status message depending on if the phone number is successfully verified, then reveal the status-box.

<% if @phone_number.verified %>
  $('#verify-pin').hide()
  $('#status-box').removeClass()
  $('#status-box').addClass('alert alert-success')
  $('#status-message').text('Success!!')
<% else %>
  $('#status-box').removeClass()
  $('#status-box').addClass('alert alert-warning')
  $('#status-message').text("Sorry, that wasn't the right pin.")
<% end %>
$('#status-box').fadeToggle()

That’s it!

rails-sms-phone-verification

Next Steps

This should get the ball rolling on verifying phone numbers in your Rails app, but it’s not a complete yet. You’ll want to code up some answers to a few questions:

  • What if the entered phone number is already verified?
  • What’s the UX if they get the PIN wrong? Should there be a resend PIN button?
  • What keeps someone from bruteforcing the PIN? Should the PIN expire after X tries or Y minutes?

If you have any questions or suggestions on this tutorial, or would like to show off what you’ve used phone verification for, hit me up at gb@twilio.com or @greggyb.

Happy Hacking!