'Share session data between Flask apps in separate Docker containers that are served using a reverse proxy

I have a Docker app running on localhost. There are multiple flask apps each in its own container. One is on the root (localhost) and the others are on subdomains (app.localhost). I use a traefik reverse proxy to serve the containers.

Without any authentication, the whole Docker app works great.

Then I try to authenticate the users. I am using Microsoft Authentication Library(Azure AD) for this. On the root app (localhost) this works great. If I am not logged in, it redirects me to a login page with a link. I click the link and now I am authorized. I am also able to pull the username from the http header.

However, when I go to a subdomain (app.localhost), it forgets I am logged in and then crashes because I try to run the same code of pulling the username from the http header, but it is missing.

Code for root app:

app = Flask(__name__)
app.config.from_object(app_config)
Session(app)

# login functions ######################################################################################
def _load_cache():
    cache = msal.SerializableTokenCache()
    if session.get("token_cache"):
        cache.deserialize(session["token_cache"])
    return cache

def _save_cache(cache):
    if cache.has_state_changed:
        session["token_cache"] = cache.serialize()

def _build_msal_app(cache=None, authority=None):
    return msal.ConfidentialClientApplication(
        app_config.CLIENT_ID, authority=authority or app_config.AUTHORITY,
        client_credential=app_config.CLIENT_SECRET, token_cache=cache)

def _get_token_from_cache(scope=None):
    cache = _load_cache()  # This web app maintains one cache per session
    cca = _build_msal_app(cache)
    accounts = cca.get_accounts()
    if accounts:  # So all accounts belong to the current signed-in user
        result = cca.acquire_token_silent(scope, account=accounts[0])
        _save_cache(cache)
        return result

@app.route('/login/')
def login():
    session["state"] = str(uuid.uuid4())
    auth_url = _build_msal_app().get_authorization_request_url(
        app_config.SCOPE,
        state=session["state"],
        redirect_uri=url_for("authorized", _external=True))

    return render_template('login.html', auth_url=auth_url)

@app.route("/auth")  # This absolute URL must match your app's redirect_uri set in AAD
def authorized():
    if request.args['state'] != session.get("state"):
        return redirect(url_for("login"))
    cache = _load_cache()
    result = _build_msal_app(cache).acquire_token_by_authorization_code(
        request.args['code'],
        scopes=app_config.SCOPE,
        redirect_uri=url_for("authorized", _external=True))
    if "error" in result:
        return "Login failure: %s, %s" % (
            result["error"], result.get("error_description"))
    session["user"] = result.get("id_token_claims")
    _save_cache(cache)
    return redirect(url_for("index"))

def get_token(scope):
    token = _get_token_from_cache(scope)
    if not token:
        return redirect(url_for("login"))
    return token

@app.route("/logout")
def logout():
    session.clear()  # Wipe out the user and the token cache from the session
    return redirect(  # Also need to log out from the Microsoft Identity platform
        "https://login.microsoftonline.com/common/oauth2/v2.0/logout"
        "?post_logout_redirect_uri=" + url_for("index", _external=True))

# actual app ##########################################################################################
@app.route('/')
def index():
    # send to login page if user is not logged in
    if not session.get('user'):
        return redirect(url_for('login'))
    else:
        return render_template('index.html')

app_config.py

from os import environ

CLIENT_SECRET = environ["CLIENT_SECRET"]
AUTHORITY = environ["AUTHORITY"]
CLIENT_ID = environ["CLIENT_ID"]
SCOPE = ["User.ReadBasic.All"]
SESSION_TYPE = "filesystem"

A copy of this app_config file is in the directories for each flask app.

I tried adding this to the app_config files, but apparently localhost doesn't work as the cookie domain.

SESSION_COOKIE_NAME='localhost'
SESSION_COOKIE_DOMAIN='localhost'
REMEMBER_COOKIE_DOMAIN='localhost'

Then I read somewhere that dev.localhost could work. So I changed the Docker app to run on dev.localhost instead of localhost and added this to the app_config.

SESSION_COOKIE_NAME='dev.localhost'
SESSION_COOKIE_DOMAIN='dev.localhost'
REMEMBER_COOKIE_DOMAIN='dev.localhost'

This seemed liked it may work, but Microsoft doesn't allow dev.localhost/auth to be a redirect uri.

What do I need to do for the session to carry between subdomains/other flask apps?

Unfortunately I have to use Windows containers on a Windows Server 2019. I know they are not the best, but it is what I have to work with.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source