@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.