Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Secure your C# / ASP.NET Core app by validating incoming Twilio requests


(warning)

Warning

See the ASP.NET version of thisguide.

In this guide, we'll cover how to secure your C# / ASP.NET Core(link takes you to an external page) 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 filter attribute for our ASP.NET app that uses the Twilio C# SDK(link takes you to an external page)'s validator utility. This filter will then be invoked on the controller actions that accept Twilio webhooks to confirm that incoming requests genuinely originated from Twilio.

Let's get started!

(information)

Info

If you don't want to develop your own validation filter, you can install the Twilio helper library for ASP.NET Core(link takes you to an external page) and use the library's [ValidateRequest] attribute instead that has more features. This library also contains an endpoint filter and a middleware validator.


Create a custom filter attribute

create-a-custom-filter-attribute page anchor

The Twilio C# SDK includes a RequestValidator class we can use to validate incoming requests.

We could include our request validation code as part of our controller, but this is a perfect opportunity to write an action filter attribute(link takes you to an external page). This way we can reuse our validation logic across all our controllers and actions that accept incoming requests from Twilio.

Use filter attribute to validate Twilio requests

use-filter-attribute-to-validate-twilio-requests page anchor

Confirm incoming requests to your controllers are genuine with this filter.


_57
using System;
_57
using System.Collections.Generic;
_57
using System.Linq;
_57
using System.Threading.Tasks;
_57
using Microsoft.AspNetCore.Http;
_57
using Microsoft.AspNetCore.Mvc;
_57
using Microsoft.AspNetCore.Mvc.Filters;
_57
using Microsoft.Extensions.Configuration;
_57
using Twilio.Security;
_57
_57
namespace ValidateRequestExample.Filters
_57
{
_57
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
_57
public class ValidateTwilioRequestAttribute : TypeFilterAttribute
_57
{
_57
public ValidateTwilioRequestAttribute() : base(typeof(ValidateTwilioRequestFilter))
_57
{
_57
}
_57
}
_57
_57
internal class ValidateTwilioRequestFilter : IAsyncActionFilter
_57
{
_57
private readonly RequestValidator _requestValidator;
_57
_57
public ValidateTwilioRequestFilter(IConfiguration configuration)
_57
{
_57
var authToken = configuration["Twilio:AuthToken"] ?? throw new Exception("'Twilio:AuthToken' not configured.");
_57
_requestValidator = new RequestValidator(authToken);
_57
}
_57
_57
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
_57
{
_57
var httpContext = context.HttpContext;
_57
var request = httpContext.Request;
_57
_57
var requestUrl = $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
_57
Dictionary<string, string> parameters = null;
_57
_57
if (request.HasFormContentType)
_57
{
_57
var form = await request.ReadFormAsync(httpContext.RequestAborted).ConfigureAwait(false);
_57
parameters = form.ToDictionary(p => p.Key, p => p.Value.ToString());
_57
}
_57
_57
var signature = request.Headers["X-Twilio-Signature"];
_57
var isValid = _requestValidator.Validate(requestUrl, parameters, signature);
_57
_57
if (!isValid)
_57
{
_57
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
_57
return;
_57
}
_57
_57
await next();
_57
}
_57
}
_57
}

To validate an incoming request genuinely originated from Twilio, we first need to create an instance of the RequestValidator class passing it our Twilio Auth Token. Then we call its Validate method passing the requester URL, the form params, and the Twilio request signature.

That method will return True if the request is valid or False if it isn't. Our filter attribute then either continues processing the action or returns a 403 HTTP response for forbidden requests.


Use the filter attribute with our Twilio webhooks

use-the-filter-attribute-with-our-twilio-webhooks page anchor

Now we're ready to apply our filter attribute to any controller action in our ASP.NET application that handles incoming requests from Twilio.

Apply the request validation filter attribute to a set of controller methods

apply-the-request-validation-filter-attribute-to-a-set-of-controller-methods page anchor

Apply a custom Twilio request validation filter attribute to a set of controller methods used for Twilio webhooks.


_37
using Microsoft.AspNetCore.Mvc;
_37
using Twilio.TwiML;
_37
using ValidateRequestExample.Filters;
_37
_37
namespace ValidateRequestExample.Controllers
_37
{
_37
[Route("[controller]/[action]")]
_37
public class IncomingController : Controller
_37
{
_37
[ValidateTwilioRequest]
_37
public IActionResult Voice(string from)
_37
{
_37
var message = "Thanks for calling! " +
_37
$"Your phone number is {from}. " +
_37
"I got your call because of Twilio\'s webhook. " +
_37
"Goodbye!";
_37
_37
var response = new VoiceResponse();
_37
response.Say(string.Format(message, from));
_37
response.Hangup();
_37
_37
return Content(response.ToString(), "text/xml");
_37
}
_37
_37
[ValidateTwilioRequest]
_37
public IActionResult Message(string body)
_37
{
_37
var message = $"Your text to me was {body.Length} characters long. " +
_37
"Webhooks are neat :)";
_37
_37
var response = new MessagingResponse();
_37
response.Message(message);
_37
_37
return Content(response.ToString(), "text/xml");
_37
}
_37
}
_37
}

To use the filter attribute with an existing controller action, just put [ValidateTwilioRequest] above the action's definition. In this sample application, we use our filter attribute with two controller actions: one that handles incoming phone calls and another that handles incoming text messages.

(information)

Info

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 ASP.NET application sees does not match the URL Twilio used to reach your application.

To fix this for local development with ngrok, use http:// for your webhook instead of https://. To fix this in your production app, your method will need to reconstruct the request's original URL using request headers like X-Original-Host and X-Forwarded-Proto, if available.

Before running the application, make sure you configure your Twilio Auth Token(link takes you to an external page) as the Twilio:AuthToken configuration, using .NET's secrets manager(link takes you to an external page), environment variables, a vault service, or some other secure configuration source.


Disable request validation during testing

disable-request-validation-during-testing page anchor

If you write tests for your controller actions, those tests may fail where you use your Twilio request validation filter. Any requests your test suite sends to those actions will fail the filter's validation check.

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

An improved request validation filter attribute, useful for testing

an-improved-request-validation-filter-attribute-useful-for-testing page anchor

Use this version of the custom filter attribute if you test your controllers.


_66
using System;
_66
using System.Collections.Generic;
_66
using System.Linq;
_66
using System.Threading.Tasks;
_66
using Microsoft.AspNetCore.Hosting;
_66
using Microsoft.AspNetCore.Http;
_66
using Microsoft.AspNetCore.Mvc;
_66
using Microsoft.AspNetCore.Mvc.Filters;
_66
using Microsoft.Extensions.Configuration;
_66
using Twilio.Security;
_66
_66
namespace ValidateRequestExample.Filters
_66
{
_66
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
_66
public class ValidateTwilioRequestAttribute : TypeFilterAttribute
_66
{
_66
public ValidateTwilioRequestAttribute() : base(typeof(ValidateTwilioRequestFilter))
_66
{
_66
}
_66
}
_66
_66
internal class ValidateTwilioRequestFilter : IAsyncActionFilter
_66
{
_66
private readonly RequestValidator _requestValidator;
_66
private readonly bool _isEnabled;
_66
_66
public ValidateTwilioRequestFilter(IConfiguration configuration, IWebHostEnvironment environment)
_66
{
_66
var authToken = configuration["Twilio:AuthToken"] ?? throw new Exception("'Twilio:AuthToken' not configured.");
_66
_requestValidator = new RequestValidator(authToken);
_66
_isEnabled = configuration.GetValue("Twilio:RequestValidation:Enabled", true);
_66
}
_66
_66
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
_66
{
_66
if (!_isEnabled)
_66
{
_66
await next();
_66
return;
_66
}
_66
_66
var httpContext = context.HttpContext;
_66
var request = httpContext.Request;
_66
_66
var requestUrl = $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}";
_66
Dictionary<string, string> parameters = null;
_66
_66
if (request.HasFormContentType)
_66
{
_66
var form = await request.ReadFormAsync(httpContext.RequestAborted).ConfigureAwait(false);
_66
parameters = form.ToDictionary(p => p.Key, p => p.Value.ToString());
_66
}
_66
_66
var signature = request.Headers["X-Twilio-Signature"];
_66
var isValid = _requestValidator.Validate(requestUrl, parameters, signature);
_66
_66
if (!isValid)
_66
{
_66
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
_66
return;
_66
}
_66
_66
await next();
_66
}
_66
}
_66
}

To disable the request validation, you can now configure Twilio:RequestValidation:Enabled to false in your appsettings.json or appsettings.Development.json file.


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.

To learn more about securing your ASP.NET MVC application in general, check out the security considerations page in the official ASP.NET docs(link takes you to an external page).


Rate this page: