twilio / twilio-python

A Python module for communicating with the Twilio API and generating TwiML.
MIT License
1.81k stars 692 forks source link

Twilio authentication always fails #715

Open hklchung opened 1 year ago

hklchung commented 1 year ago

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

  1. Set up a test voice application with Python Flask, and using Ngrok tunnel
  2. Get the host, request url and scope
  3. 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:

charan678 commented 1 year ago

Hello @hklchung Can you please refer this end-end test link? Let me know if this helps.

charan678 commented 1 year ago

@hklchung did you use this guide ?

hklchung commented 1 year ago

@hklchung did you use this guide ?

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.

Any advice on how to proceed would be highly appreciated 111 . 222

charan678 commented 1 year ago

hello @hklchung It's working for me. Following are the steps followed by me

  1. 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)
  2. open active phone number page and add ngrok http url to webhook for both messaging and voice

  3. 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)
  1. 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

hklchung commented 1 year ago

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://?

joshkulick commented 5 months ago

Please check out my solution that I introduced issues, This was super frustrating in having to debug