Session State Not Persisting After Redirect - MSAL Authentication Issue in Streamlit

Hi Streamlit Community,

I am building an Azure Entra ID authentication flow in Streamlit using the MSAL (Microsoft Authentication Library).
I am encountering an issue where the authentication flow (flow_json) is missing after redirect, causing the authentication process to fail.
The Issue:

  1. The login button initializes the MSAL authentication flow successfully.
  2. After clicking the login link, I authenticate on Microsoft Entra ID.
  3. After successful login, Streamlit redirects back, but st.session_state["flow_json"] is None, causing authentication failure.
  4. This results in the error:

python
ERROR: flow_json is missing or None. Authentication cannot proceed.

What I Have Tried

  • Persisting flow_json in st.session_state before redirection.
  • Restoring flow_json after redirection.
  • Comparing state values to prevent CSRF issues.
  • Checking if Streamlit clears session state after redirects.

Code Snippet:

python
import streamlit as st
import msal
import requests
import os
import json

— :white_check_mark: CONFIGURATION —

AAD_AUTHORITY = os.getenv(“AZURE_AAD_AUTHORITY”)
AAD_CLIENT_ID = os.getenv(“AZURE_CLIENT_ID”)
AAD_CLIENT_SECRET = os.getenv(“AZURE_CLIENT_SECRET”)
REDIRECT_URI = “http://localhost:8501” (Running in local for now, otherwise deploying via azure web app)
SCOPES = [“User.Read”]

— :white_check_mark: SESSION STATE INITIALIZATION —

if “token” not in st.session_state:
st.session_state[“token”] = None
if “username” not in st.session_state:
st.session_state[“username”] = None
if “flow_json” not in st.session_state:
st.session_state[“flow_json”] = None
if “auth_state” not in st.session_state:
st.session_state[“auth_state”] = None

— :white_check_mark: AUTH FUNCTIONS —

def initialize_flow():
“”“Start MSAL authentication flow.”“”
pca = msal.ConfidentialClientApplication(AAD_CLIENT_ID, authority=AAD_AUTHORITY, client_credential=AAD_CLIENT_SECRET)
flow = pca.initiate_auth_code_flow(SCOPES, redirect_uri=REDIRECT_URI)

# ✅ Save in session before redirecting
st.session_state["flow_json"] = json.dumps(flow)
st.session_state["auth_state"] = flow["state"]

return flow

def acquire_token_by_auth_code_flow(flow, query_params):
“”“Acquire token after redirect.”“”
if not isinstance(flow, dict):
st.error(“:warning: Error: ‘flow’ is missing.”)
return None

auth_response = dict(query_params)
expected_state = flow.get("state", None)
received_state = auth_response.get("state", None)

if expected_state != received_state:
    st.error(f"⚠️ State mismatch error: Expected {expected_state}, Received {received_state}")
    return None

try:
    result = app.acquire_token_by_auth_code_flow(flow, auth_response)
    if "access_token" in result:
        st.session_state["token"] = result
        st.session_state["username"] = result.get("id_token_claims", {}).get("preferred_username", "User")
        st.experimental_rerun()
    return result
except Exception as ex:
    st.error(f"⚠️ Error acquiring token: {ex}")
    return None

— :rocket: STREAMLIT UI —

st.title(“:locked_with_key: Entra ID Authentication”)

app = msal.ConfidentialClientApplication(AAD_CLIENT_ID, authority=AAD_AUTHORITY, client_credential=AAD_CLIENT_SECRET)

if not st.session_state[“token”]:
if not st.query_params:
if st.button(“Login with Entra ID”):
flow = initialize_flow()
auth_url = flow[“auth_uri”]
st.write(f":link: Click here to login", unsafe_allow_html=True)
else:
flow = json.loads(st.session_state[“flow_json”]) if st.session_state[“flow_json”] else None
if flow:
result = acquire_token_by_auth_code_flow(flow, st.query_params)
if result and “access_token” in result:
st.session_state[“token”] = result
st.session_state[“username”] = result.get(“id_token_claims”, {}).get(“preferred_username”, “User”)
st.success(f":white_check_mark: Logged in as {st.session_state[‘username’]}")
st.experimental_rerun()
else:
st.error(“:cross_mark: Authentication failed. Try again.”)
st.session_state[“flow_json”] = None
st.stop()
else:
st.error(“:cross_mark: ERROR: flow_json is missing. Authentication cannot proceed.”)

My Questions:

  1. Why is st.session_state["flow_json"] missing after redirect?
  • Does Streamlit clear session state on redirects?
  • If so, what is the best way to persist authentication flow across redirects?
  1. How can I correctly restore the authentication flow after redirecting?
  • Should I store flow_json differently (e.g., use st.cache, used but didnt work either)?

Any help would be appreciated! :folded_hands:

When your identity provider redirects back to your app, it starts a new session. Nothing from the previous session’s Session State will be available.

If you only authentication (identity) and not authorization (permissions) of your users, you can try using the built-in functionality with st.login(). Otherwise, you’ll need to look at a third-party component to correctly handle leaving and returning to the app.

Thanks for your response! I saw that the latest Streamlit 1.43.0+ officially supports OpenID Connect authentication with Microsoft Entra ID using st.login(), so I tried implementing it. However, I ran into some issues.

What We Tried with st.login()

:one: Configured secrets.toml as per the docs:
We set up the required fields in .streamlit/secrets.toml:

toml

[auth]
redirect_uri = “http://localhost:8501/oauth2callback”
cookie_secret = “xxx”

[auth.microsoft]
client_id = “xxx”
client_secret = “xxx”
server_metadata_url = “https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration”


2️⃣ Implemented `st.login()` in the app:

python


import streamlit as st

if not st.experimental_user.is_logged_in:
    st.login("microsoft")  # Using the Entra ID provider
else:
    st.write(f"Hello, {st.experimental_user.name}!")

:three: Tried logging in, but faced these problems:

Login button doesn’t work in some cases: Clicking it sometimes doesn’t trigger the authentication flow.
Redirect doesn’t complete: After logging into Entra ID, it returns to the app but stays on the login page
Session Issues: Even when we successfully get redirected, st.experimental_user is not populated with the user’s details.
Token Handling: Streamlit’s st.login() does not return the access token. We need this to interact with Microsoft Graph API.

Question:
As per your knowledge, has anyone successfully used st.login() with Entra ID, myself is pretty new to streamlit .

Thankyou for your response

Yes, the example in the docstrings is from a validated test I ran and I wrote out my steps in this tutorial: Use Microsoft Entra to authenticate users - Streamlit Docs :slight_smile:

Thanks for the link, I have tried the code and many debugging sessions but it doesn’t seems to resolve it. I get redirected back to login page after logging in with credentials.

I seem to be facing the exact same issue when trying st.login().
The authentication seems to be successful and the auth_code is returned in the redirect_uri. But the page shows an error.
Has anyone managed to get this working with Microsoft Entra?

I solved it, I used easy auth by azure web app, Configure Microsoft Entra Authentication - Azure App Service | Microsoft Learn . Please refer to this and maybe this solution work for you.

1 Like

I have a similar error. I am using Microsoft Entra ID Authentication with st.login(). I have setup the secrets file as well as the Entra ID client properly with correct redirect URLs. Strangely the login works, redirects properly and sets everything in st.user when I run in local. But when I run the same thing in AWS cloud by deploying it in a docker container on ECS Fargate, it authenticates and redirects but does not set anything in st.user. Can someone help me understand what is going wrong?

Are there any plans to allow custom redirects? with extra params? If the user is in the dashboard section I don’t want to redirect them to home just because they logged in. Thanks

I’m not aware of anything specifically, but I recommend filing a feature request because that’s the best way for devs to know where the most interest is. Our product managers and engineers actively track the upvotes on GitHub issues.