Friday, 29 November, 2019 UTC


Summary

How to secure Twilio webhook URLs in Node.js
Twilio’s APIs allow developers to reinvent communications with things like programmable phone calls, SMS or intelligent chatbots. Developers can build applications to interact with their users and react to their responses. To respond to events like incoming messages, you can define a webhook URL. A webhook is an HTTP request that Twilio performs to find out what the reaction to a Twilio even like an incoming SMS should be. Your defined HTTP endpoints have to respond with a Twilio-understandable configuration language called TwiML (Twilio Markup Language).
These endpoints can be hosted anywhere as long as they’re available publicly and accessible by Twilio’s infrastructure.
Three ways to secure your webhook URLs
Let’s assume you built a rating application that allows users to rate an event they’re at by sending an SMS to a certain number. Every incoming SMS to your number triggers a webhook to your infrastructure that can write the included data to disk.
Unfortunately, an evil hacker found your webhook HTTP endpoint and recognizes the responded TwiML response as Twilio configuration. The hacker is now able to make requests to your application, which messes with the result of your event rating and makes it useless.
Securing your webhook URLs is always recommended. But as the Twilio docs describe, it’s crucial if you expose sensitive data or mutate existant datasets like in our rating example. How can you make sure only Twilio is interacting with your endpoints?
First, make sure you’re using HTTPS. Man-in-the-middle attacks are a common risk, and if you’re not serving your Twilio configuration over a secure connection, you can never be sure that a third party didn’t alter it. What you could do additionally is to secure your endpoints with HTTP authentication. This way your webhook URLs are not accessible without a username/password combination in the first place.
A third option is to validate if the incoming requests were sent by Twilio using the X-Twilio-Signature header which is the core of this article. Let’s have a look at how this works in detail!
The X-Twilio-Signature header
When Twilio sends a request to your defined webhook URL, it will include the x-twilio-signature header. This header is an encoded string representing the request URL and the sorted request parameters. The resulted string is then signed using HMAC-SHA1 using your AuthToken as the key. The AuthToken is crucial here because its value is only accessible to Twilio and yourself. A third party can not generate the same hashed Twilio signature without having access to it.
To validate incoming requests, you can perform the same signature procedure and compare if your generated signature and the sent signature match. If they do, you can be sure that Twilio sent the request you received.
Sidenote: in case you’re using serverless functions via Twilio Runtime, you might have seen the access control setting, which enables Twilio signature validation for every incoming request.

X-Twilio-Signature validation using the Twilio Node.js helper library

To make it as easy as possible, the Twilio Node.js helper library not only provides you with functionality to build TwiML responses or interact with the API endpoints, it also includes the feature to validate requests.
To get started, make sure you installed the library.
npm i twilio 
After you installed it, you can use the validateRequest method that is exposed to the exported Twilio object. The method accepts four arguments:
  • your Twilio AuthToken
  • the x-twilio-signature header included in the incoming request
  • the URL your HTTP is accessible at
  • the included request parameters
In Node.js you can use the validateRequest method in express as follows:
const twilio = require('twilio'); const express = require('express'); const bodyParser = require('body-parser');  const app = express(); const port = 8080;  app.use(bodyParser.urlencoded({ extended: true }));  app.post('/sms', (req, res) => {  const twilioSignature = req.headers['x-twilio-signature'];  const params = req.body;  const url = 'https://your-webhook-endpoint.io';   const requestIsValid = twilio.validateRequest(  process.env.TWILIO_AUTH_TOKEN,  twilioSignature,  url,  params  );   if (!requestIsValid) {  return res.status(401).send('Unauthorized');  }   // write to a database, call other services, ...  // then respond to the message!  res.set({  'Content-Type': 'text/plain'  });  res.send("You're allowed!"); });   app.listen(  port,  () => console.log(`Example app listening on port ${port}!`) ); 
validateRequest is the recommended way to validate incoming webhooks. It works with any framework out there and I hope you start securing your endpoint URLs today. If you’re using express, too, the Twilio library also provides a middleware to make request validation with this popular framework easier.

Things happening under the hood

If you’re curious how to generate the signature yourself, you can have a look at the docs that explain it in detail or have a look at the following example code:
/**  * Get a valid Twilio signature for a request   *  * @param {String} authToken your Twilio AuthToken  * @param {String} URL your webhook URL  * @param {Object} params the included request parameters  */ function getSignature(authToken, url, params) {  // get all request parameters  var data = Object.keys(params)  // sort them  .sort()  // concatenate them to a string  .reduce((acc, key) => acc + key + params[key], url);   return crypto  // sign the string with sha1 using your AuthToken  .createHmac('sha1', authToken)  .update(Buffer.from(data, 'utf-8'))  // base64 encode it  .digest('base64'); } 
Not every request comes from where you think it comes from
I hope this article brings light into the question of how you can make sure that the request hitting your webhook URLs are really Twilio’s and not just a person tinkering with your infrastructure. If you want to see how you can make your webhook URLs more secure using express, you can have a look at this tutorial. You can also have a look at how to secure your webhooks in Ruby or Python. And that’s it friends, stay safe! See you next time!

If you have questions or want to say “Hi” you can reach me under the following channels: