CORS preflight request freezes our app

Hello folks,

in need some help with a tricky CORS problem :wink:

We are currently developing a small Dash application. Our app is hosted in the cloud, so we need some sort of security. We have connected our app to our enterprise identity management system (KeyCloak).
Therefore we use the framework flask_oidc (1.4.0). We configured the client_secrets.json and protect every view-function of our dash-app:

    def _protect_dashviews(dash_app):
        for view_func in dash_app.server.view_functions:
            if view_func.startswith(dash_app.config.url_base_pathname):
                dash_app.server.view_functions[view_func] = oidc.require_login(
                    dash_app.server.view_functions[view_func])

This works pretty fine. On the initial visit of our app the user is redirected to the login page of our idm and after a successful login back to our app. Except of one point: After some time our oidc token seems to time out. When the page _/dash-update-component is called, the javascript behind this component sends a HTTP-Request to /auth/realms/appid-0798/protocol/openid-connect/auth?xyz. Sadly this request is declined because of CORS. The preflight request of this request gets the following response:

Access to fetch at 'https://login.idm.company.com/auth/realms/appid-0798/protocol/openid-connect/auth?xyz (redirected from ‘https://app.cloud.net/_dash-update-component’) from origin ‘https://app.cloud.net’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

I understand why this preflight request is sent. And i understand that the responding idm system needs to respond with a ‘Access-Control-Allow-Origin’ header. This could be a possible fix, but in the moment it is not possible to modify the settings of the idm system.
Is there any chance to fix this on our side (inside the app)? Is there any possibility to modify the component or request, so no preflight request is sent?

Thanks in advance.
Regards, Florian

4 Likes

I am having this EXACT same issue with MSAL python protecting dash views with access/refresh tokens to Microsoft Graph. Were you able to solve this problem with your app and if so how?

Hello @Bw984 ,

sadly i was not able to solve this problem entirely, but i believe this is a problem of our keycloak (idm).
I solved this problem by omitting the refresh of the token. We just do an initial login at the first visit of the session and save a flag in the cookie if the login was successful.

I hope the following code snippet helps to understand:
I use the following framework: flask_dance

init.py

oauth_blueprint = OAuth2ConsumerBlueprint(
    "xy-oauth", __name__, client_id="xy", login_url='/login',
    client_secret=os.getenv('IDM_CLIENT_SECRET'),
    base_url=IDM_BASE_URL,
    token_url=IDM_BASE_URL + "token",
    authorization_url=IDM_BASE_URL + "auth",
    authorized_url='/oidc_callback')
app.server.register_blueprint(oauth_blueprint, url_prefix="/")

def require_login(view_func):
    """
    Use this to decorate view functions that require a user to be logged
    in. If the user is not already logged in, they will be sent to the
    Provider to log in, after which they will be returned.

    .. versionadded:: 1.0
       This was :func:`check` before.
    """

    @wraps(view_func)
    def decorated(*args, **kwargs):
        if not oauth_blueprint.session.authorized:
            return redirect(url_for("xy-oauth.login"))
        return view_func(*args, **kwargs)
    return decorated


def _protect_dashviews(app):
    for view_func in app.server.view_functions:
        if view_func.startswith(app.config.url_base_pathname):
            app.server.view_functions[view_func] = require_login(
                app.server.view_functions[view_func])

app.py

_protect_dashviews(app)
app.run_server(host='0.0.0.0', debug=False)

Let me know if you have further questions.

Regards, Florian

I tracked my problem down to multiple gunicorn workers working on the same set of callbacks after a user interaction. I did not have flask sessions setup through a common database such as redis and some of the gunicorn workers were getting re-directed to my MS login page since they didn’t have a logged in user in THEIR session.

I was able to fix the issue by changing my flask session system to redis, now each user has a true single source session object to utilize to verify auth. I tracked down the issue by going through the network tab on Chrome’s developer tools. I couldn’t figure out why my login page was calling dash update components and it lead me down the path of discovery regarding sessions and gunicorn workers.

Hi @CSS_Fanboy and @Bw984

I stumbled upon a similar problem you’ve dealt with here.

When deploying my dash app behind a authentication proxy (provided by microsoft), I’m getting CORS related errors in a scenario when user first loads the dash app (everything works perfectly), then leaves the the browser window open for an extended period of time (e.g. 1 hour), then comes back and tries to interact with the app UI, the app is not responsive because of the following errors (found using the chrome console):

Access to fetch at 'https://login.microsoftonline.com/...' (redirected from 'dash_app.example.com/:1 https://dash_app.example.com/dash-update-component') from origin 'https://dash_app.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

GET https://login.microsoftonline.com/... dash_renderer.v2_3_1m1719224514.min.js:2
net::ERR_FAILED

Error: Callback failed: the server did not respond.
at dash_renderer.v2_3_1..514.min.js:2:103020

What is perhaps different in my case compared to yours is that I have no user management logic in my dash app. No user profiles, no flask_dance etc.

I’m, however also using gunicorn.

@Bw984 do you think your solution with the redis-based flask session setup would help in my case?

Thanks for help!

After ~45min to 1hr of of time the user will need to acquire a new access token from Microsoft using the refresh token in the session. When using gunicorn each worker is independent and shares no information with the other workers. In my app each gunicorn worker restarts after 1000 requests to prevent memory leak pressure so on busy work days we are starting dozens of new workers, so you cannot rely on a worker to house the auth token information.

I haven’t used the Azure authentication proxy personally but I would guess your problems are virtually identical to mine. I use redis.com and for $7/mo I find the service extremely useful. I use redis for flask session mgt and any other data that needs to be shared between workers.

1 Like

Thank you very much for this info!

I actually already use Redis (self-hosted as a part of my docker compose stack for my dash app). Until now, I’ve been using Redis only for caching and for long callbacks. I’ll look into how to also use it for session management. If you could point me to a good tutorial or share a code snippet from your setup, it would be much appreciated! Nevertheless, already now you helped me a lot. Thank you! :slight_smile: