Secure your Python Amazon Lambda App by Validating Incoming Twilio Requests
Today we're going to look at how to secure your Amazon Lambda Python app by validating that incoming requests to your webhook are, in fact, actually coming from Twilio. We'll cover loading external Python libraries on Lambda, passing a header through Amazon API Gateway, and validating requests in Amazon Lambda with the Twilio Python Helper Library.
If you haven't yet visited our receiving and replying to messages in Python on Amazon Lambda guide, we highly suggest you visit that first. This application will build on top of the code and setup in that guide.
If you've already gone through that guide? Excellent... let's do some work to keep out the imposters!
Passing The X-Twilio-Signature Header through API Gateway
Our first stop on this security flavored trip is to the Amazon API Gateway console. In order to pass a received Header from API Gateway through to Amazon Lambda and our Python logic, we need to explicitly call it out.
From this point forward, we're going to assume you have built the example application from the receiving and replying to messages in Python on Amazon Lambda guide.
Modifying the Method Request to Keep X-Twilio Signature
Click through to your API, and from inside 'Resources' expand the Twilio API. At that point click on the 'POST' method that we set up in the Reply Guide:
Next, click the 'Method Request' link, and expand the 'HTTP Request Headers' section. In there, you should add one new header: X-Twilio-Signature
. When you are done, it should look like this:
Return to Method Execution by clicking the link at the top of the frame - you may need to scroll up to see it.
Adding X-Twilio-Signature to the JSON Passed to our Lambda Function
Next, click the 'Integration Request' link. Expand the 'Body Mapping Templates' section, and click on the application/x-www-form-urlencoded
mapping set up for the reply guide.
Change the mapping to pass through the X-Twilio-Signature with the following code:
#set($httpPost = $input.path('$').split("&")) { "twilioSignature": "$input.params('X-Twilio-Signature')", #foreach( $kvPair in $httpPost ) #set($kvTokenised = $kvPair.split("=")) #if( $kvTokenised.size() > 1 ) "$kvTokenised[0]" : "$kvTokenised[1]"#if( $foreach.hasNext ),#end #else "$kvTokenised[0]" : ""#if( $foreach.hasNext ),#end #end #end }
And that's it for API Gateway! Don't forget to Re-Deploy the API by selecting 'Deploy API' from the 'Actions' menu:
Validating Requests in Amazon Lambda
Now, navigate to the Amazon Lambda Console and select your Twilio function.
You will need to write code and install packages on your development machine before loading everything to Lambda. On your computer, create a new directory with a single file named 'twilio_function.py'. Within that file, paste the contents of the default function from the Amazon console. We're going to look at how to use external libraries in Amazon Lambda!
Including the Twilio Python Helper Library in Amazon Lambda
Amazon has an excellent primer on how to add external Python packages to Lambda. We found using a global pip the easiest method of setup; if you'd like to try our method it follows this paragraph. If you don't, feel free to skip ahead to the next section.
- Use pip (for Python 2.7) with the -t (target) flag to install packages in your directory:
pip install twilio -t /PATH/TO/NEW/DIRECTORY/WITH/TWILIO_FUNCTION.PY
If your global pip is for 3.x, on OSX or *NIX run:
python2.7 -m pip install twilio -t /PATH/TO/NEW/DIRECTORY/WITH/TWILIO_FUNCTION.PY
If your global pip is for 3.x and you are on Windows, run:
py -2.7 -m pip install twilio -t /PATH/TO/NEW/DIRECTORY/WITH/TWILIO_FUNCTION.PY
Warning if on OSX with a Homebrew-managed pip Install: If pip refuses to install packages in this directory, create a new file in the directory namedsetup.cfg
and the following contents:[install] prefix=
- Zip all of the resulting files - including
twilio_function.py
andsetup.cfg
- in the directory. You should not include the parent directory, only the inside. - Inside the Lambda console change the 'Code Entry Type' pulldown to 'Upload a .ZIP File'. Hit the 'Upload' button and navigate to the zip file we just created. The 'Save' button will upload the entire package.
- Change the 'Code Entry Type' back to 'Edit code inline'. Now, paste the complete contents (the 'Clipboard' button on this page will copy the whole function) of our example Twilio function into the box and 'Save' the function.
- From the configuration tab, change only the 'Handler' field to
<FILENAME_MINUS_DOT_PY>.<FUNCTION_NAME>
. If you're following our conventions, that should be 'twilio_function.handler': - 'Save', and hopefully you'll have no errors. If you do, double check you have only zipped the interior of the new directory and have a proper Handler and single check the other steps.
Once you're successful, we can move back to Lambda's Inline Editor in the 'Code' tab's 'Code Entry Type'.
Feel free to copy the complete twilio_function.py into your Inline Editor at this point. We're going to cover some of the sections piecemeal so you can better see how validation works. Finally, we're going to set a few environmental variables directly from the console and then we'll be ready to test!
Preparing to Validate Twilio Requests
While many Python web frameworks such as Flask and Django (see the 'Related Guides' section of the sidebar) handle dividing the body and headers and escaping and unescaping strings automatically, on Lambda you'll have to do a bit of preparation first. It's still rather simple since we can use some simple filtering and the built-in Python library urllib. Just note that the helper library expects unescaped strings and no headers in the passed dictionary.
Validation itself is very simple. Behind the scenes, Twilio's helper library is hashing the incoming request with HMAC-SHA1 and your Auth Token (from the Twilio console) as a key to validate against X-Twilio-Signature
. Additionally, we've added a simple check that the 'From' parameter matches a MASTER_NUMBER
environmental variable, which might be a useful example for your application.
Now we just have to set some environmental variables, test our API, and hopefully get the secret message MMS'd to us. Carry on!
Setting Environmental Variables on Amazon Lambda
Environmental variables on Lambda are set directly from the console. In this application, we've demonstrated three - AUTH_TOKEN
, MASTER_NUMBER
, and REQUEST_URL
. Directly below the Inline Editor, you should see an entry for environmental variables.
In those fields, set AUTH_TOKEN
to the authentication token from your Twilio Console. MASTER_NUMBER
should be set to whatever number you will be using to test the application. REQUEST_URL
is the same url you have given to Twilio as your webhook; if you need this, navigate to Numbers (#) in the console, click the number you are using, and check the primary handler.
Here's where you would populate those variables:
Great! Now let's make sure we keep the wrong folks out while letting you get the secret message.
Text the message 'Secret' (capitalization doesn't matter) from your MASTER_NUMBER
. If all goes well you should get a strangely reassuring MMS from an AI. If you text 'secret' from another number, you should see a slightly more unsettling SMS.
And, just like in the Reply Guide, any message without a body of 'secret' will give a classic "Hello, World" response.
What’s Next for Your App?
Validating requests to your API Gateway webhooks in your Lambda function is an excellent step in your journey to a secure application. Next, we'd suggest reading our extensive security documentation for our best advice on securing your application. We particularly recommend the Anti-Fraud Developer's Guide, where we discuss some fraudulent behavior we've witnessed in the past.
You're well on the path to a hardened Python application with Amazon Lambda now. We're very excited to see where you're headed next - let us know in the comments or through Twitter when you're ready to ship!
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.