Search code examples
flaskazure-active-directoryplotly-dashflask-dance

Use of https in Dash app AAD authentication with flask-dance


I'm developing a Dash app based on Flask, which has integration with Azure AD via flask-dance. I followed this tutorial: https://dev.to/kummerer94/aad-auth-for-plotly-dash-3m57. While it works well in the development environment, in production deployment it redirects to the wrong URI, since it uses http instead of https.

This is my code: it is a minimal Dash app.

from dash import Dash, html
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask, redirect, url_for, session
from flask_dance.contrib.azure import azure, make_azure_blueprint
from dotenv import load_dotenv

load_dotenv()

def login_required(func):
    """Require a login for the given view function."""

    def check_authorization(*args, **kwargs):
        if not azure.authorized or azure.token.get("expires_in") < 0:
            return redirect(url_for("azure.login"))
        else:
            return func(*args, **kwargs)

    return check_authorization

blueprint = make_azure_blueprint(
    client_id="my client id",
    client_secret="my client secret",
    tenant="my tenant id",
    scope=["openid", "email", "profile"],
    login_url="/login",
    authorized_url="/auth",
    prompt="none"
)

server = Flask(__name__)
server.config["SECRET_KEY"] = "secret key"
server.register_blueprint(blueprint, url_prefix="/login")

app = Dash(__name__, server=server)

server.wsgi_app = ProxyFix(server.wsgi_app, x_proto=1, x_host=1, x_for=1, x_prefix=1, x_port=1)

for view_func in server.view_functions:
    if not view_func.startswith("azure"):
        server.view_functions[view_func] = login_required(server.view_functions[view_func])

app.layout = html.Div(children=[
  html.H1(children='Azure AD test'),
  html.Div(children="You are logged in!")
])

if __name__ == '__main__':
    app.run(debug=True)

The production deployment is done via Microsoft IIS and wfastcgi. After being redirected to the Microsoft login site, the following error is received:

"...The redirect URI 'http://example.com/login/auth' specified in the request does not match the redirect URIs configured for the application 'my client id'"

The problem is that the redirect URI specified in Azure AD has https instead of http. This is what I've tried so far:

  • Using ProxyFix (werkzeug.middleware.proxy_fix), with x_proto, x_for, etc. set to 1.
  • Setting Flask config params APPLICATION_ROOT="\" and PREFERRED_URL_SCHEME="https".
  • Setting the env variable OAUTHLIB_INSECURE_TRANSPORT=0 (in fact that's the content of the .env file that is loaded with load_dotenv()). If set to 1, as suggested in Flask-Dance and Error: redirect_uri_mismatch, the same error pops up.
  • Changing the debug parameter to False.
  • (EDIT) Since first posting the question I also tried using flask-talisman by adding the line Talisman(server). A "connection refused error" was returned. Setting content_security_policy=None had no effect.

None of it seems to work as the redirect URI built by Flask is still wrong (http instead of https). It should be pointed out that using http is out of the question. Any ideas?

Thanks in advance.


Solution

  • Per my comments below made 2 hours after this response..

    I am wondering if you should perhaps do the ProxyFix IMMEDIATELY after creating the server in case the blueprint creation happens immediately (i.e. in your case, before the ProxyFix has been applied). Might help.

    I was just about to post essentially the same issue... this on AWS...(see below) It is very strange as my environment is a copy of one I have used twice before successfully.

    Hi

    I have a config that has worked in the past (other environments) but is misbehaving for me now.

    I am running an ec2 behind an AWS ALB with the ALB mapping 443 to 80 on the ec2 which is then picked up by nginx in a docker container and routed to a python flask app running oauth using flask-dance to azure... I have wrapped the app in werkzeug proxyfix.

    app = Flask(__name__)
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1)
    

    My problem is that despite the nginx config looking like this:

    upstream webapp {
        server amalfinationv2:5005;
    }
    
    server {
        listen 5085;
        server_name localhost;
    
        location / {
            proxy_pass http://webapp;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Proto https;
            proxy_buffers 8 16k;
            proxy_buffer_size 32k;    }
    
    }
    

    And this appearing to work for standard calls i.e. redirects in flask but in the flask-dance case it is not a redirect but an argument passed to the call out to microsoft.

    My calls to the azure blueprint keep setting the redirect_uri argument on the call to microsoft auth to use http not https. I have tried forcing the issue by building the blueprint as shown below...

    azure_blueprint = make_azure_blueprint(
        client_id=utils_instance.get_secret("AZURE_ID"),
        client_secret=utils_instance.get_secret("AZURE_SECRET"),
        redirect_url="https://v2.amalfination.com/login/azure/authorized",
    )
    

    But this seems to make no difference to the redirect_uri parameter on the call.

    I strongly suspect this is a weird setting somewhere in the long path but am having a lot of trouble finding it.

    My question is, if (as is the default and usually works for me) no redirect_url is passed, the azure_blueprint seems to build that value itself - how do I find where that happens so I can perhaps further debug the source of the problem (naturally made harder because it is only exhibiting on the AWS environment). Worst case I can inject some old fashioned print statement in there to see if I can find the culprit.

    The azure_blueprint seems to be a structure that defeats my minimal flask/blueprint understandings.

    Any pointers, suggestions appreciated.

    regards Andrew