Securing Your Ruby Webhooks with Rack Middleware

September 11, 2014
Written by
Phil Nash
Twilion

rack-logo

Hello fellow Rubyist, I see you have your shiny new Twilio application written, perhaps a Rails app or Sinatra, like in our Ruby quickstart guide. Before you hit that deploy button, you need to stop and ask yourself a couple of questions about that app. Do your webhooks expose sensitive data when they receive a request? Do they change data within your system? If your answer to either of those questions is yes, then you should be making sure that your app only responds to requests that come from Twilio.

You can do this using Twilio’s request validation. Twilio will cryptographically sign all requests that it makes to your application. We create a signature using your callback URL, the request parameters and your auth token then add an HTTP header, X-Twilio-Signature, containing that signature. On your server, you can follow the same process to create the signature and check if it matches, if it doesn’t the request can be rejected. There are more details available in the security documentation.

Until recently, if you wanted to secure your endpoints using the twilio-ruby gem you could use the Twilio::Util::RequestValidator class. The gem documentation has an example of how to do this, but you have to adapt that to your web framework of choice. If only there was a simple, one line way to add request validation to your app.

Middleware to the Rescue

Rack is an interface for web applications in Ruby and is the foundation on which most of the popular Ruby web frameworks including Rails, Sinatra and even Camping. Further to this, Rack provides support for middleware, filters that can intercept a request and alter the response from a web application.

Middleware is an ideal place to validate our incoming requests from Twilio and other platforms have been using them to simplify that job for quite a while now. For example, Node.js developers have had request validation with middleware for the Express framework since the start of the year. And now I’m pleased to tell you that, with version 3.12.0 of the twilio-ruby gem, there is an easy to use Rack middleware that you can drop into your apps in order to validate that your incoming requests are from Twilio.

So, let’s see how easy it is to put this new middleware to work in a Rack application application.

An Example Application

To show how easy it is to use this middleware, let’s put together a very quick application using Sinatra.

First we need to install the gems that we’ll need.

$ gem install sinatra twilio-ruby

Now, create a file named app.rb and copy in the simple Sinatra application below.

require 'sinatra'
require 'twilio-ruby'

# Create a webhook that handles an incoming SMS and responds with TwiML
post '/messages' do
  response = Twilio::TwiML::Response.new do |r|
    r.Message "Hello from Sinatra!"
  end
  # Render the TWiML response
  response.to_xml
end

This application will respond to any POST request to /messages without any request validation whatsoever. It only returns the TwiML to send an SMS in response, but as this is only an example we could imagine it does something a bit more dangerous. Let’s run the app and check that this is working as we expect.

$ ruby app.rb

The application will now be running on localhost on port 4567. Let’s make a post request and get back the TwiML that we defined.

$ curl --data "chunky=bacon" http://localhost:4567/messages

You will have seen the XML response defined in the app proving that we can make a POST request with any data in it and our application will respond. Now let’s add in the Rack middleware provided by the gem and ensure that the application only responds to requests initiated by Twilio.

Securing Our Webhooks with Rack Middleware

When making a webhook request Twilio includes a header, X-Twilio-Signature, that is signed with your account auth token. This is what we use to verify the request came from Twilio. In order to do that, our application will need to know our auth token.

Keep the config in the local enviornment

I prefer to store config variables in the application’s environment, following the advice from the Twelve-Factor App. Let’s add in Dotenv to make that easy for us.

To add our auth token to our environment we’ll need to install the Dotenv gem.

$ gem install dotenv

Then we’ll need a .env file with our auth token in. Dotenv config files are written like so:

TWILIO_AUTH_TOKEN=YOUR_AUTH_TOKEN_HERE

Save that file and then add these two lines to our app.rb file:

require 'sinatra'
require 'twilio-ruby'
require 'dotenv'

Dotenv.load

post '/messages' do
  # TwiML response
end

Drop in the middleware

Now we have everything we need to add our middleware to the application and secure our /messages endpoint. After you load the config, add the following line:

use Rack::TwilioWebhookAuthentication, ENV['TWILIO_AUTH_TOKEN'], '/messages'

This tells Sinatra to use the Rack::TwilioWebhookAuthentication middleware and passes two arguments to it: our auth token and a path to secure. The path ensures that the middleware only validates requests to our Twilio endpoints, since we can’t expect everyone to send a correct X-Twilio-Signature with each request.

Let’s test this with curl to make sure we are now receiving the correct response.

$ curl --data "chunky=bacon" http://localhost:4567/messages
Twilio Request Validation Failed.

Excellent! Our app doesn’t respond with potentially sensitive data to just any request now.

We should now test that responding to requests initiated by Twilio does work. This is out of scope for this post, but you can read up on how to test your local webhooks with ngrok in this post by Kevin. When you get that set up, you should find that sending a text to your Twilio number will get a text in response.

You can actually pass multiple paths to the middleware too. If you had another endpoint defined at /voice for voice calls then you could set up the middleware like so:

use Rack::TwilioWebhookAuthentication, ENV['TWILIO_AUTH_TOKEN'], '/messages', '/voice'

Now requests to both /messages and /voice are secured.

What About Rails?

That was a simple example application in Sinatra, but thankfully it is no more difficult to add this functionality to a Rails application.

Let’s assume that you already have a Rails application with the twilio-ruby gem included and an endpoint defined at /messages, similar to the Sinatra application.

To add the middleware, you need to open up config/environments/production.rb and within the application configure block add the following line:

Rails.application.configure do
  # Other Rails config

  config.middleware.use Rack::TwilioWebhookAuthentication, ENV['TWILIO_AUTH_TOKEN'], '/messages'
end

That’s all there is to it.

We are only adding this to the production environment as that is the only one that is likely to contain sensitive information. You could add the middleware to config/environment.rb if you wanted to protect all environments.

Webhooks Secured

If you apply what we’ve gone through here to your Rack based applications your webhooks will be nice and secure. With a bit of setup and a single line of config your application will only process requests to your webhooks from Twilio.

As a bonus, if you had set up request validation before using the Twilio::Util::RequestValidator then you can replace that implementation and worry about maintaining a few less lines of code.

Are you interested in how the middleware works compared to the reference implementation of the validator? You’re welcome to check that out on GitHub. Please raise any bugs or feature requests that you may have on the GitHub issues page. Of course, if you just want to chat about how awesome Rack middleware is (what can’t it do?!) then drop me a line on Twitter or shoot me an email.