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