Secure Your Node.js Webhooks with Middleware for Express

January 14, 2014
Written by

node600

Editor’s Note: Hey there! Are you new to Twilio and node.js? If so, you might want to check out this tutorial first to get your feet wet.

If you’re using Twilio in your node.js app with our official module, you might already be using our utility functions that help you validate that an HTTP request to your webhook was indeed originated from Twilio. This is a good idea, especially if your application will mutate data or complete some kind of transaction when a text or call is received. For more on Twilio request validation generally, you can check out this topic in our Security documentation.

In this post, however, we’re going to see how request validation can be super easy in node.js web apps that are using the popular Express web framework. Express is built around a configurable stack of middleware that handles things like cookie parsing, HTTP Basic authentication, and session management. As of version 1.5.0 of the twilio module (published in npm), there is now an easy-to-use middleware that you can use to secure Twilio webhooks. In addition, we also added some API sugar to make rendering TwiML responses a bit easier.

Let’s see how it works!

Getting Started

In a terminal window, create a new folder for a sample application that we will secure with our new Express middleware. In this folder, install both the twilio and express modules from npm:

npm install twilio express

Now, create a file app.js which will contain a simple Express web application, contained in the code below. NOTE: to use our Express request validation, you must also be using the express.urlencoded() middleware, which will parse the bodies of inbound form-encoded requests, like those that originate from Twilio.

var twilio = require('twilio'),
    express = require('express');
    
// Create an express web app
var app = express();

// Use middleware to parse incoming form bodies
app.use(express.urlencoded());

// Create a webhook that handles an incoming SMS
app.post('/sms', function(request, response) {
    // Create a TwiML response
    var twiml = new twilio.TwimlResponse();
    twiml.message('Hello from node.js!');
    
    // Render the TwiML response as XML
    response.type('text/xml');
    response.send(twiml.toString());
});

// Have express create an HTTP server that will listen on port 3000
// or "process.env.PORT", if defined
app.listen(process.env.PORT || 3000);

This code defines a webhook with Express that doesn’t implement request validation. To make sure it’s working as-is, start the node app:

node app.js

And send it a test POST request with some junk data:

curl -v -d foo=bar http://localhost:3000/sms

No matter what is sent or who sends it, our webhook returns TwiML with the message we specified. Let’s see how we can make this a bit more secure using the Twilio module’s built-in Express middleware.

Securing Our Webhooks with Express Middleware

When Twilio sends your app an HTTP request, it includes a header value that is signed with your account’s auth token, a value that only Twilio and your app should have access to. This should allow you to be certain that an HTTP request actually originated from a Twilio server. To process this header, our web app needs to be configured with our account’s auth token (found on your dashboard).

A reasonable way to do this is configuring a system environment variable for your Twilio auth token. While you could use the auth token directly in code, that’s not usually what you want to do. If you’re forgetful like me, you will slip up somewhere along the line and check your Twilio auth token in to source control. This, of course, is not a good thing – so let’s just configure our auth token as an environment variable. Enter the following into the terminal (for *nix environments):

export TWILIO_AUTH_TOKEN=your auth token string

The webhook middleware (like our REST client) will default to looking for a variable named TWILIO_AUTH_TOKEN in the system environment – in node this will become available as a global variable at process.env.TWILIO_AUTH_TOKEN. Once our system environment variable is set, let’s change our webhook code to secure our app against non-Twilio requests:

// Note the second argument after '/sms' that we added
app.post('/sms', twilio.webhook(), function(request, response) {
    // ... same as before ...
});

Stop and restart our node app to pick up the code changes (or if you find that annoying, you can of course start your webapp using tools like supervisor or nodemon). Once you have done this, try sending another HTTP request to your app on the command line:

curl -v -d foo=bar http://localhost:3000/sms

You will now get a an HTTP response with status code 403, indicating that you are forbidden from accessing this URL. This is because your curl request did not contain the proper Twilio request signature.

Let’s configure this webhook for use with a real Twilio number to ensure that it does work like we would expect.

Hitting our Webhook from a Twilio Number

When a Twilio number receives an incoming text or call, we ask your server for instructions on how you’d like to handle that call or text via a webhook. For that to work, your server needs to be accessible on the public Internet.

One way to get your app on the Internet is to use a port forwarding tool like ngrok (which is sublimely awesome). If you’d like to use ngrok, there’s a whole tutorial devoted to getting it set up here. Otherwise, you can deploy your node app wherever you like, such as a VPS or PaaS like Heroku or Nodejitsu. In this tutorial, we’ll deploy using Heroku.

Deploy your node.js app using the instructions provided by Heroku for node.js. Note that we’ve named our node app file app.js, so your Procfile for Heroku will need to be slightly different:

web: node app.js

You’ll also need to export your TWILIO_AUTH_TOKEN as a Heroku config setting. To do this, execute the following command – this assumes that you have previously exported your auth token in your local system environment:

heroku config:add TWILIO_AUTH_TOKEN=$TWILIO_AUTH_TOKEN

This should export your auth token as a system environment variable on Heroku and restart your web process.

Next, go to your Twilio numbers and click on one that you can use for development (or buy one). Configure your Heroku app URL as shown. Don’t forget to click “Save Changes” when you’re done!

twilio number configuration

Now, send a text message to your Twilio number. Now that Twilio is requesting your URL with the proper signed HTTP request, you should be getting the message in return on your phone!

API Sugar

The new Express middleware also makes it slightly nicer to send back TwiML Response objects in Express route handlers. In app.js, replace the /sms handler code with the below:

// Create a webhook that handles an incoming SMS
app.post('/sms', twilio.webhook(), function(request, response) {
    // Create a TwiML response
    var twiml = new twilio.TwimlResponse();
    twiml.message('Hello from node.js!');
    
    // Render the TwiML response as XML
    response.send(twiml);
});

Note that we no longer need to manually set the response’s Content-Type header, or call toString() on the TwiML Response object. The twilio.webhook middleware monkey patches the Express response.send function to make it aware of TwiML responses, and behave the right way when it is passed one of them. You can re-deploy your app to Heroku to ensure the webhook works the same as it did before.

Disabling Validation

Under certain conditions, you may want to disable request validation. To do this, simply pass in the appropriate option to the twilio.webhook function to change the behavior of the middleware:

// Create a webhook that handles an incoming SMS
app.post('/sms', twilio.webhook({
    validate:false
}), function(request, response) {
    // Create a TwiML response
    var twiml = new twilio.TwimlResponse();
    twiml.message('Hello from node.js!');
    
    // Render the TwiML response as XML
    response.send(twiml);
});

You might also want to disable request validation unless your code is running in production. Express and other node frameworks will often look for the environment type in process.env.NODE_ENV (this is set to production by default in Heroku environments). Conditional code to enable request validation only in production might look like this:

// Create a webhook that handles an incoming SMS
app.post('/sms', twilio.webhook({
    validate:process.env.NODE_ENV === 'production'
}), function(request, response) {
    // Create a TwiML response
    var twiml = new twilio.TwimlResponse();
    twiml.message('Hello from node.js!');
    
    // Render the TwiML response as XML
    response.send(twiml);
});

Conclusion

If your Twilio app changes data in response to a call or text, you should probably be using request validation. While this has always been possible in node.js, the new Express middleware in the Twilio module makes validating webhook requests especially painless. It also adds in some API sugar which can make your code more terse.

If you have any questions or feedback on this functionality, we would love to hear it. You can discuss feature requests or module bugs (gasp!) on our GitHub issues page. If you need help with your code, your best bet is to work with our amazing support squad. Or, if you just want to shoot the breeze, feel free to reach out to me on Twitter. Happy JavaScripting!