Part 2: Using Twilio SMS with Sinatra for Ruby and Datamapper To Build A Phone Verification System

About a year ago, we published an update to our phone verification tutorial to teach you how to keep your users safe and prevent fraud when building applications in PHP. Now you can learn how to employ the same security and anti-spam techniques in Ruby using the elegant Sinatra framework. (update: we also have a tutorial on how to build SMS phone verification into your Rails app).

For any application that interacts with phones, you will need to be able to verify a user’s phone number to prevent fraud, spam and mistakes when entering numbers. One of the methods of verifying a number is by using an automated phone call and prompting the user to enter a code. However, since we already covered that method of verification in our previous PHP phone verification post, today we are going to verify the user’s number using an SMS message.

In this post we cover how to build a simple phone number verification system using Twilio SMS with Sinatra for Ruby, Heroku, and DataMapper. DataMapper is a fast, thread-safe, and feature rich Object Relational Mapper written in Ruby.

You can download the complete example from Github here, but we will go into detail about every segment of the code in this post. You can run this example locally or deploy it to your Ruby application. hosting platform of choice.

The 6 Steps For Building a Phone Verification System

User visits verification web page and enters phone number.

Random verification code is generated and sent via SMS to the user.

User enters the code that they received into a web form.

If code is entered correctly, update database.

If code is entered incorrectly, re-prompt user to enter code.

Update web page with status message.

Step One: The Phone Number Verification Pages

To begin, we need to create a views folder to hold all of our erb template files. Then we’re going to create a basic ERB template to display a simple form to the user and display an error if they enter their number incorrectly. Create a file called index.erb and add a basic form to enter a phone number, as demonstrated in the following HTML:

In a production application, we would want to make sure the phone number is formatted properly as E.164 before the user submits the form, but for the purposes of this tutorial we will simply return an error if it is not a valid number.

Next we will create a file called register.erb, which is the view displayed to the user upon entering their phone number for the first time:

XHTML

1

<%if@error==true%>

Please try again, there was an error verifying your number!

XHTML

1

<%end%>

You have been sent a verification code via SMS!

When you receive it, enter the verification code below:

Phone Number: Verification Code:

This view prompts the user for the verification code that they were sent via text message, and then prompts them to re-enter it if there is an error.

The last view we will need to create is verified.erb, which is the page displayed to the user upon successful verification:

XHTML

1

<%if@verified==true%>

You have already verified that number!

XHTML

1

<%end%>

Congratulations, you have successfully verified your number <%= @phone_number %>
Verify another number here

You will note that we also display an alert if the number has already been verified, we do not let the user verify the same number more than once.

Step Two: Initializing Our Application

Now that all of our views are created, we can go back to our main directory and start coding. The first thing we need to do is set up our Gemfile with the following required dependencies. The most important things to note here are that we are using the sinatra web framework, the data_mapper gem for storing our verified phone numbers in a SQLite database and the twilio-ruby gem to communicate with the Twilio SMS API. We also use the rotp gem to allow us to generate secure, time-based one-time passwords for verification.

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

source'https://rubygems.org'

gem"sinatra"

gem"sinatra-contrib"

gem"twilio-ruby"

gem"sanitize"

gem“rotp”

gem"data_mapper"

gem"sqlite3"

gem"dm-sqlite-adapter"

Now run a quick bundle install in your Terminal to install all of these required gems. Once that process is completed, we can jump right in and start writing our application code in a new file called app.rb
To start with, add the following code to the top of app.rb:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

require"sinatra"

require"sinatra/multi_route"

require"data_mapper"

require"twilio-ruby"

require"sanitize"

require"erb"

require“rotp”

includeERB::Util

DataMapper::Logger.new(STDOUT,:debug)

DataMapper::setup(:default,"sqlite3://#{Dir.pwd}/dev.db")

classVerifiedUser

includeDataMapper::Resource

property:id,Serial

property:code,String,:length=>10

property:phone_number,String,:length=>30

property:verified,Boolean,:default=>false

end

DataMapper.finalize

DataMapper.auto_upgrade!

This code requires all of our necessary gems and also configures our DataMapper resource model, called VerifiedUser. Our VerifiedUser model only has 4 parameters – an auto-incremented id, a phone number, the verification code and a flag to determine whether or not the number has already been verified.

We then finish up by automatically migrating our database to the latest model definition. This is an extremely convenient feature of DataMapper, as it eliminates the need to write manual migrations or maintain your database schema using queries rather than application code.

Next we are going to add the following block to app.rb:

Ruby

1

2

3

4

5

6

7

8

9

10

beforedo

@twilio_number=ENV['twilio_number']

@client=Twilio::REST::Client.newENV['account_sid'],ENV['auth_token']

ifparams[:error].nil?

@error=false

else

@error=true

end

end

This block creates a new instance of Twilio::REST::Client, which lets us easily communicate with the Twilio API via our twilio-ruby gem. We are storing our account credentials in environment variables, so that they are not hardcoded in our application. Hardcoding your API credentials can be a security risk if you are sharing your code with other collaborators. We also store an SMS-enabled Twilio phone number in an environment variable so that we can use it later to send the user their verification code via text message.
You can set these variables by using the following shell commands and replacing the dummy strings with your valid Account Sid, Auth Token, and Phone Number that you have purchased in your Twilio dashboard.

1

2

3

export account_sid=ACxxxxxxxxxxxxxxxxxxxxxxxx

export auth_token=yyyyyyyyyyyyyyyyyyyyyyyyy

export twilio_number=+12121234567

We end our before block by detecting whether or not an error has been reported in our GET parameters. As you may have noticed in the views above, we use this error flag to tell the user if their phone number or verification code is not valid.

Step Three: Adding Application Logic

Now we can start creating our routes. The first route we need to create is our index, which displays the initial registration form:

Ruby

1

2

3

get"/"do

erb:index

end

This is extremely simple, and simply renders our index.erb view for the user. Next we need to create our register route, which handles the initial user creation and code generation aspects of our application:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

route:get,:post,'/register'do

@phone_number=Sanitize.clean(params[:phone_number])

if@phone_number.empty?

redirectto("/?error=1")

end

begin

if@error==false

user=VerifiedUser.first_or_create(:phone_number=>@phone_number)

ifuser.verified==true

@phone_number=url_encode(@phone_number)

redirectto("/verify?phone_number=#{@phone_number}&verified=1")

end

totp=ROTP::TOTP.new("drawtheowl")

code=totp.now

user.code=code

user.save

@client.account.sms.messages.create(

:from=>@twilio_number,

:to=>@phone_number,

:body=>"Your verification code is #{code}")

end

erb:register

rescue

redirectto("/?error=2")

end

end

Note that we sanitize our user input before querying the database. We use this user input to either update or create an entry in our VerifiedUser table – we use first_or_create to ensure that we do not have duplicate phone number entries.

If the user is already verified, we redirect them to the verification page so that we do not verify them again. If they have not yet verified their number, we generate a new code using the Ruby ‘rotp’ One-Time Password gem, save it in the VerifiedUser table, and send it to them using Twilio SMS. We then render our register.erb view which prompts the user to enter the code they received as a text message.

Lastly, we have our verify route:

Ruby

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

route:get,:post,'/verify'do

@phone_number=Sanitize.clean(params[:phone_number])

@code=Sanitize.clean(params[:code])

user=VerifiedUser.first(:phone_number=>@phone_number)

ifuser.verified==true

@verified=true

elsifuser.nil?oruser.code!=@code

@phone_number=url_encode(@phone_number)

redirectto("/register?phone_number=#{@phone_number}&error=1")

else

user.verified=true

user.save

end

erb:verified

end

You may have noticed that both our verify and register routes support GET and POST requests. This is because of how we handle errors using the sinatra redirect helper. It is simpler to allow the routes to handle multiple methods than to duplicate code.

In this method we look up the user again. If they have not verified their number already, we confirm that their code is correct and set their verified flag to true. If it is false or if there is some error, we redirect them to the verification page.

And that’s it, run the application with ruby app.rb and try it out!

Now you can go ahead and start adding SMS-based phone verification to your own applications to keep your users safe and add another level of trust to your account system.
If you would like to see the full, working example you can find it on Github

Here are some tips for when you implement similar code in a production environment:

Always make sure to sanitize your user inputs

Consider using JavaScript to make the user experience more seamless

Validate phone numbers in the browser before attempting to send an SMS message

Add verification via phone call as a backup method

If you have any questions or comments please tweet @jonmarkgo or e-mail jonmarkgo@twilio.com!