Interference between concurrent users sessions

this is being mentioned around on and on…

I work with streamlit 1.40.2 @ python 3.12 latest, did not implement any caching (except st.fragment decorators), have containerized the app and host it myself as a microservice routed by nginx. I have also implemented supabase.com authentication and streamlit cookie manager from the community to automatically reauthentificate users.
– All works well in the production!

However, when another user visits the page (from another device! other location!) they see the dashboard of the currently logged user. As a matter of fact in my case, they get automatically authenticated too. Likewise, other user can log out the first user…
– What a mess!!!

My app actively uses session-state to store user IDs and other user data to work across multiple streamlit pages. I did my reading but cannot conclusion what causes this behavior. Any ideas what goes wrong here?

Test it out on your own, it is live> careerprofiles.online

Can you share the code you use to sign people in? (e.g. If you can’t share your app’s full code, can you reproduce the issue with just a login screen and share that as a fully executable example?)

@mathcatsand, see my home.py (entrypoint) that handles user authentication and forwards them to /account.py (user dashboard) if successful:

if "authentication_status" not in ss:
    ss.authentication_status = None  # default (not failed, not success)
if "userPrivileges" not in ss:
    ss.userPrivileges = ["public"]
if "userID" not in ss:
    ss.userID = None

if CookieManagerInstance.get(cookie="streamlit_cookie"):  # if expected cookie exists...
    stored_refresh_token_value = CookieManagerInstance.get(cookie="streamlit_cookie")

    try:
        # Get confirmation from supabase that stored refresh token is valid
        response = Supabase.auth.refresh_session(
            stored_refresh_token_value
        )  # this exchanges tokens in databaes (old for new!!!)

        # Store new refresh token value as cookie value
        CookieManagerInstance.set(
            cookie="streamlit_cookie",
            val=response.session.refresh_token,  # store new supabase refresh token as cookie value
            expires_at=calculate_future_date(30),  # prolong expiration
            secure=True,
        )  # Expires in a day by default

        # Set authentification status true
        ss.authentication_status = True

        # Decode supabase response
        decoded_response = jwt.decode(
            response.session.access_token,
            algorithms=["HS256"],
            options={"verify_signature": False},
        )
        # Get userID from it
        ss["userID"] = decoded_response["sub"]

    except AuthApiError:
        pass  # streamlit cookie exists but it is not valid anymore (expired, i.e.)

if ss["authentication_status"]:
    try:
        ss["userDetails"] = PgOperator.fetch_records(
            model=User,
            userID=ss["userID"],
        ).to_ModelList()[0]  # expecting single record!
        ss["userPrivileges"] = ss["userDetails"].privileges  # store user privilege to session
        ss["userAccess"] = generate_user_access(
            ss["userPrivileges"]
        )  # user authorization details

        resume_configurator = PrintConfigurator(
            existing_print_config=ss.userDetails.print_configuration,
            db_update_func=PgOperator.bulk_update_models,
            userDetails=ss.userDetails,
        )

        # Ensure each user gets default config (if none for new users) or complete list of default key-values (if extending print config)
        resume_configurator.preset_default_values_at_log_in()

        st.switch_page("pages/account.py")  # swich to account pages

    except IndexError as e:  # catch the problem when local database does not return what is expected (i.e. Stephan's email address with capital letter)
        st.error("Not possible to sign in. Please contact admin.")
        time.sleep(2)  # give user time to read the message
        logger.error(f"User: {ss["userID"]} was not able to sign in: {e}")  # log error
        Supabase.auth.sign_out()  # clears internal state
        ss["authentication_status"] = None  # sets the auth in session from scratch
        ss["userPrivileges"] = ["public"]  # not needed, but lets clear it
        ss["userID"] = None  # not needed, but lets clear it
        ss["userDetails"] = None  # not needed, but lets clear it
        ss["userAccess"] = None  # not needed, but lets clear it
        CookieManagerInstance.delete(cookie="streamlit_cookie")

else:
    # Instaciate operator
    UserDataOperator = UserDataOps(
        require_admin_verification=False,
    )

    sign_in_tab, sign_up_tab = st.tabs(["Sign in", "Sign up"])
    with sign_in_tab:
        navigate_privileged_menu(ss.userPrivileges)  # at this moment=[public]

        auth_response: object | None = UserDataOperator.sign_in_form(
            session=ss, session_key_for_auth_response="auth_response"
        )  # allow user to sign up and catch object return by supabase

        if auth_response:
            ss["authentication_status"] = True
            ss["userID"] = UUID(auth_response.user.id)  # str to UUID!

            CookieManagerInstance.set(
                cookie="streamlit_cookie",
                val=auth_response.session.refresh_token,  # use supabase refresh token (jwt encoded by supabase) as cookie value
                expires_at=calculate_future_date(30),
                secure=True,
            )  # Expires in a day by default

    with sign_up_tab:
        # # Render form and get registred user data
        UserDataOperator.sign_up_form()

        # # Render registration button to confirm
        UserDataOperator.button_menu()

Meanwhile I advanced in figuring out that the automatic re-authentication logic I have implemented might be the issue…

My CookieManagerInstance (an instance of class wrapper around extra_streamlit_components cookie manger) sets the cookie key as CookieManagerInstance.set(cookie="streamlit_cookie", .... ) when auth succeeds. Then, I test if “streamlit_cookie” in cookies and if, I pass the visitor to /account.py without the need to sign up .

When I removed my reauthentification step, the concurrent users session work well! However, in this case users have to sign up every time, also when opening another browser tab… which kind if sucks.

Knowing that, the tricky question I am wondering about is: Why does streamlit sets “streamlit_cookie” for another visitor (literally, another person visiting from other physical location, device and browser) automatically when one visitor is logged in the application at the moment?

This is as much I am able to reason what is going on on my case. I hope that helps to advance the discussion.

I do not want to share whole codebase, besides, there are too many dependencies as the base got bigger. But it it would experts to have a loot at it, I can prepare simplest setup possible with the same stack and push it to public repo.