Securing Your Ruby Webhooks with Rack Middleware
Time to read: 7 minutes
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.
Now, create a file named app.rb and copy in the simple Sinatra application below.
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.
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.
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.
Then we’ll need a .env file with our auth token in. Dotenv config files are written like so:
Save that file and then add these two lines to our app.rb file:
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:
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.
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:
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:
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.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.