Twilio authentication always fails
hklchung opened this issue · comments
Issue Summary
I am developing a Python (Flask) voice app on a local machine and using Ngrok tunnel to allow Twilio access the app via a webhook (all set up according to the Twilio guide). I need to add a step to check the requests are coming from Twilio and am using the RequestValidator from the Twilio Python SDK. However, this step always returns false, but should be true as I am the one initiating the request during testing.
Steps to Reproduce
- Set up a test voice application with Python Flask, and using Ngrok tunnel
- Get the host, request url and scope
- Run RequestValidator()
Code Snippet
In your Python Flask test voice application, set up a /call app route, then inside the call() test the following
validator = RequestValidator(TWILIO_AUTH_TOKEN)
path = request.path
headers = dict(request.headers)
host = headers.get('Host')
url = f"http://{host}{path}"
body = request.data
validator.validate(request.url, headers, body)
Technical details:
- twilio-python version: 8.1.0
- python version: 3.8.8
- Flask version: 2.2.2
- pyngrok version: 6.0.0
Hi @charan678 this is the guide I have been following. I have also double checked to ensure the Twilio webhook URL starts with http://
since I am using Ngrok.
I tried to run test_url() but unfortunately all returned with a response code 404.
hello @hklchung It's working for me. Following are the steps followed by me
- setup ngrok
- run ngrok config command ( ngrok config add-authtoken XXXXXXXXX). here XXXXXX is your ngrok authToken
- run ngrok http command (ngrok --scheme http http 5000)
-
open active phone number page and add ngrok http url to webhook for both messaging and voice
-
I used this code below to test and export TWILIO_AUTH_TOKEN before running in local env
from flask import Flask, request, abort
from twilio.twiml.voice_response import VoiceResponse
from twilio.twiml.messaging_response import MessagingResponse
from twilio.request_validator import RequestValidator
from functools import wraps
import os
app = Flask(__name__)
def validate_twilio_request(f):
"""Validates that incoming requests genuinely originated from Twilio"""
@wraps(f)
def decorated_function(*args, **kwargs):
# Create an instance of the RequestValidator class
validator = RequestValidator(os.environ.get('TWILIO_AUTH_TOKEN'))
# Validate the request using its URL, POST data,
# and X-TWILIO-SIGNATURE header
request_valid = validator.validate(
request.url,
request.form,
request.headers.get('X-TWILIO-SIGNATURE', ''))
print("Request valid = ", request_valid)
# Continue processing the request if it's valid, return a 403 error if
# it's not
if request_valid:
return f(*args, **kwargs)
else:
return abort(403)
return decorated_function
@app.route('/voice', methods=['POST'])
@validate_twilio_request
def incoming_call():
"""Twilio Voice URL - receives incoming calls from Twilio"""
# Create a new TwiML response
resp = VoiceResponse()
# <Say> a message to the caller
from_number = request.values['From']
body = """
Thanks for calling!
Your phone number is {0}. I got your call because of Twilio's webhook.
Goodbye!""".format(' '.join(from_number))
resp.say(body)
# Return the TwiML
return str(resp)
#
@app.route('/message', methods=['POST'])
@validate_twilio_request
def incoming_message():
"""Twilio Messaging URL - receives incoming messages from Twilio"""
# Create a new TwiML response
resp = MessagingResponse()
# <Message> a text back to the person who texted us
body = "Your text to me was {0} characters long. Webhooks are neat :)" \
.format(len(request.values['Body']))
resp.message(body)
# Return the TwiML
return str(resp)
@app.route('/health', methods=['GET'])
def healthcheck():
return str("received")
if __name__ == '__main__':
app.run(debug=True)
- check your flask server is running on 5000 port
- Call on your active phone number and see the flask logs
- Its displaying 200 status code and request valid True for me
Technical Details
twilio-python = 8.1.0
python version = 3.8.8
os = mac os
Test phone number location = us ( +1 XXX )
Flask version = 2.2.2
refer:
How to Use Ngrok to Send Automatic Textback SMS Using POST & GET API Requests
how-to-secure-your-flask-app-by-validating-incoming-twilio-requests
Thanks @charan678, your answer provided me new direction to test in a more granular level. I found a working solution.
The webhook on the Twilio console must first be set to http://
, Ngrok tunnels can have both http://
and https://
(no need to change anything here). But the tricky part is when the request comes through to the app, the request URL is of course a http://
, this needs to be manually changed back to https://
before sending into the RequestValidator().
Would you know why this change is required, considering the webhook URL set in the console is http://
and not a https://
?