Azure-Samples / ms-identity-python-webapp

A Python web application calling Microsoft graph that is secured using the Microsoft identity platform
MIT License
284 stars 135 forks source link

Question: Use with reverse proxy. #51

Closed Litchfield-TWOSE closed 3 years ago

Litchfield-TWOSE commented 3 years ago

First, I have successfully implemented this on a number of internal application, and I am extremely grateful for the work that you did to put this together. Thank you!

My question is how to make this work with a reverse proxy. In order to have my Flask application secure (HTTPS) I need to use a reverse proxy (IIS). I have the application itself running successfully, however, when I try to log on (using your amazing code as inspiration), the REDIRECT_PATH shows up as the host URL, not the Reverse Proxy URL. Is there a way to override this so my staff can successfully log in?

ERROR: Sorry, but we’re having trouble signing you in.

AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: ....

Thank you! Robert

idg-sam commented 3 years ago

Hi Robert, Have you tried fixing the headers using ProxyFix as per these lines in the sample?

The proxy should have added X-Forwarded-Host headers when forwarding the request. The numbers in the x_host param is used to determine which header Flask uses to set HTTP_HOST, SERVER_NAME, and SERVER_PORT, in order to determine the external URI of the endpoints when using url_for('endpoint', _external=True). If you are behind more than one proxy, you'd need to adjust this number.

Likewise, the x_proto param is for determining which protocol (http/https) header value to use based on the X-Forwarded-Proto headers that the proxies have added.

https://werkzeug.palletsprojects.com/en/1.0.x/middleware/proxy_fix/ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto

The instructions for writing your own custom proxy fixer are found here.

Litchfield-TWOSE commented 3 years ago

idg-sam, I had the proxy_fix, but not with the suggested perimeters. I quickly made the change, but it did not fix the issue. The URL that is created by the code (_build_auth_url) still brings up the "host" URL, not the proxy URL.

I will read through the documents you linked to in order to see if I can find the solution. Thank you for this!! I will return to respond with our without a working solution.

I did try changing the _build_auth_url, but that gave me a different error: "AADSTS500112: The reply address 'http://srvedmwebapp01.universe.local:9000/getAToken' does not match the reply address 'https://scms.twose.ca/getAToken' provided when requesting Authorization code."

idg-sam commented 3 years ago

Can you try incrementing the numbers, i.e., setting x_proto=2 and x_host=2 ?

Litchfield-TWOSE commented 3 years ago

I just tried that (also x_proto=3 and x_host=3) and it didn't correct the issue. It still captures the original (not proxy) URL in the code: url_for("authorized", _external=True).

From what I can figure...MS checks to see the URL that is sending them the request, and that is the internal one srvedmwebapp01. MS is not "seeing" the proxy URL at all...which I do not understand why.

Litchfield-TWOSE commented 3 years ago

Reading through the documents...it might be important that I add that I am using Waitress (https://docs.pylonsproject.org/projects/waitress/en/stable/) as the server behind the IIS reverse proxy.

I'm reading through some documentation I didn't see before about Waitress and proxies... :(

idg-sam commented 3 years ago

Hi Robert, If Werkzeug's ProxyFix isn't resolving the issue, then when the app is deployed to this server at this external address, the app must make use of a custom proxy fix as follows, and remove the one from Werkzeug.

class CustomProxyFix(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        environ['HTTP_HOST'] = 'example.org'
        environ['wsgi.url_scheme'] = 'https'
        return self.app(environ, start_response)

app.wsgi_app = CustomProxyFix(app.wsgi_app)

Replace example.org with your external hostname and let me know how it goes! :)

Litchfield-TWOSE commented 3 years ago

BANG!!!!! That did it. Your the best idg-sam!!!

I've been banging my head against the wall/keyboard for 2 days before I reached out here. I just couldn't figure out what the issue was as my reverse proxy knowledge is relatively thin and I thought I had screwed up something there.