Secure your PHP/Lumen app by validating incoming Twilio requests

In this guide we’ll cover how to secure your Lumen application by validating incoming requests to your Twilio webhooks are, in fact, from Twilio.

With a few lines of code we’ll write a custom middleware for our Lumen app that uses the Twilio PHP SDK’s RequestValidator utility. We can then use that middleware on our Lumen routes which accept Twilio webhooks to confirm that incoming requests genuinely originated from Twilio.

Let’s get started!

Create custom middleware

The Twilio PHP SDK includes a RequestValidator utility which we can use to validate incoming requests.

We could include our request validation code as part of each Lumen route, but this is a perfect opportunity to write Lumen middleware. This way we can reuse our validation logic across all our routes which accept incoming requests from Twilio.

To validate an incoming request genuinely originated from Twilio, we need to call the $requestValidator->validate(...). That method will return true if the request is valid or false if it isn’t. Our middleware then either continues processing the view or returns a 403 HTTP response for unauthorized requests.

Loading Code Samples...
Language
SDK Version:
  • 5.x
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;
use Twilio\Security\RequestValidator;

class TwilioRequestValidator
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
      // Be sure TWILIO_APP_TOKEN is set in your .env file.
      // You can get your app token in your twilio console https://www.twilio.com/console
      $requestValidator = new RequestValidator(env('TWILIO_APP_TOKEN'));

      $isValid = $requestValidator->validate(
        $request->header('X-Twilio-Signature'),
        $request->fullUrl(),
        $request->toArray()
      );

      if ($isValid) {
        return $next($request);
      } else {
        return new Response('You are not Twilio :(', 403);
      }
    }
}
Use Twilio SDK `RequestValidator` to validate webhook requests.
Create Lumen middleware to handle and validate requests

Use Twilio SDK `RequestValidator` to validate webhook requests.

Apply the request validation middleware to webhooks

Apply a custom Twilio request validation middleware to all Lumen routes used for Twilio webhooks.

To use the middleware with your routes, first, you must add the middleware to bootstrap/app.php in the Register Middleware section:

$app->routeMiddleware([
  'TwilioRequestValidator' => App\Http\Middleware\TwilioRequestValidator::class,
]);

Then you must add the middleware to each route as shown here.

Loading Code Samples...
Language
SDK Version:
  • 5.x
<?php

use Illuminate\Http\Request;
use Twilio\Twiml;

$app->post('voice', ['middleware' => 'TwilioRequestValidator',
  function() {
    $twiml = new Twiml();
    $twiml->say('Hello World!');

    return response($twiml)->header('Content-Type', 'text/xml');
  }
]);

$app->post('message', ['middleware' => 'TwilioRequestValidator',
  function(Request $request) {
    $bodyLength = strlen($request->input('Body'));

    $twiml = new Twiml();
    $twiml->message("Your text to me was $bodyLength characters long. ".
                    "Webhooks are neat :)");

    return response($twiml)->header('Content-Type', 'text/xml');
  }
]);
Creates a route for /voice and /message to handle the respective webhooks.
Create Lumen routes to handle Twilio requests.

Creates a route for /voice and /message to handle the respective webhooks.

Use a tunnel for your local development environment in order to use live Twilio webhooks

If your Twilio webhook URLs start with https:// instead of http://, your request validator may fail locally when you use ngrok or in production if your stack terminates SSL connections upstream from your app. This is because the request URL that your Express application sees does not match the URL Twilio used to reach your application.

To fix this for local development with ngrok, use ngrok http 3000 to accept requests on your webhooks instead of ngrok https 3000.

Disable request validation during testing

If you write tests for your Lumen routes those tests may fail for routes where you use your Twilio request validation middleware. Any requests your test suite sends to those routes will fail the middleware validation check.

To fix this problem we recommend adding an extra check in your middleware, like shown here, telling it to only reject incoming requests if your app is running in production.

Loading Code Samples...
Language
SDK Version:
  • 5.x
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;
use Twilio\Security\RequestValidator;

class TwilioRequestValidator
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
      if (env('APP_ENV') === 'test') {
        return $next($request);
      }

      // Be sure TWILIO_APP_TOKEN is set in your .env file.
      // You can get your app token in your twilio console https://www.twilio.com/console
      $requestValidator = new RequestValidator(env('TWILIO_APP_TOKEN'));

      $isValid = $requestValidator->validate(
        $request->header('X-Twilio-Signature'),
        $request->fullUrl(),
        $request->toArray()
      );

      if ($isValid) {
        return $next($request);
      } else {
        return new Response('You are not Twilio :(', 403);
      }
    }
}
Use `APP_ENV` environment variable to disable request validation.
Disable Twilio request validation when testing

Use `APP_ENV` environment variable to disable request validation.

What’s next?

Validating requests to your Twilio webhooks is a great first step for securing your Twilio application. We recommend reading over our full security documentation for more advice on protecting your app, and the Anti-Fraud Developer’s Guide in particular.

Learn more about setting up your php development environment.

To learn more about securing your Lumen application in general, check out the security considerations page in the official Lumen docs.

Jose Oliveros
Andrew Baker
Agustin Camino
Kevin Whinnery

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
SDK Version:
  • 5.x
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;
use Twilio\Security\RequestValidator;

class TwilioRequestValidator
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
      // Be sure TWILIO_APP_TOKEN is set in your .env file.
      // You can get your app token in your twilio console https://www.twilio.com/console
      $requestValidator = new RequestValidator(env('TWILIO_APP_TOKEN'));

      $isValid = $requestValidator->validate(
        $request->header('X-Twilio-Signature'),
        $request->fullUrl(),
        $request->toArray()
      );

      if ($isValid) {
        return $next($request);
      } else {
        return new Response('You are not Twilio :(', 403);
      }
    }
}
SDK Version:
  • 5.x
<?php

use Illuminate\Http\Request;
use Twilio\Twiml;

$app->post('voice', ['middleware' => 'TwilioRequestValidator',
  function() {
    $twiml = new Twiml();
    $twiml->say('Hello World!');

    return response($twiml)->header('Content-Type', 'text/xml');
  }
]);

$app->post('message', ['middleware' => 'TwilioRequestValidator',
  function(Request $request) {
    $bodyLength = strlen($request->input('Body'));

    $twiml = new Twiml();
    $twiml->message("Your text to me was $bodyLength characters long. ".
                    "Webhooks are neat :)");

    return response($twiml)->header('Content-Type', 'text/xml');
  }
]);
SDK Version:
  • 5.x
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;
use Twilio\Security\RequestValidator;

class TwilioRequestValidator
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
      if (env('APP_ENV') === 'test') {
        return $next($request);
      }

      // Be sure TWILIO_APP_TOKEN is set in your .env file.
      // You can get your app token in your twilio console https://www.twilio.com/console
      $requestValidator = new RequestValidator(env('TWILIO_APP_TOKEN'));

      $isValid = $requestValidator->validate(
        $request->header('X-Twilio-Signature'),
        $request->fullUrl(),
        $request->toArray()
      );

      if ($isValid) {
        return $next($request);
      } else {
        return new Response('You are not Twilio :(', 403);
      }
    }
}