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:
- The login button initializes the MSAL authentication flow successfully.
- After clicking the login link, I authenticate on Microsoft Entra ID.
- After successful login, Streamlit redirects back, but
st.session_state["flow_json"]
isNone
, causing authentication failure. - This results in the error:
python
ERROR: flow_json is missing or None. Authentication cannot proceed.
What I Have Tried
- Persisting
flow_json
inst.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
â
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â]
â
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
â
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(â 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
â
STREAMLIT UI â
st.title(â 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" 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" Logged in as {st.session_state[âusernameâ]}")
st.experimental_rerun()
else:
st.error(â Authentication failed. Try again.â)
st.session_state[âflow_jsonâ] = None
st.stop()
else:
st.error(â ERROR: flow_json is missing. Authentication cannot proceed.â)
My Questions:
- 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?
- How can I correctly restore the authentication flow after redirecting?
- Should I store
flow_json
differently (e.g., usest.cache
, used but didnt work either)?
Any help would be appreciated!