Cannot instantiate authenticator object in multiple pages: DuplicatedWidgetId
I'm following the documentation at steps 1 and 2, which states that one needs to recreate the Authenticator object and call the login method on each page of a multipage app.
I'm testing the functionality but cannot recreate the object in a different page. The exception is the following:
DuplicateWidgetID: There are multiple widgets with the same key='init'.
To fix this, please make sure that the key argument is unique for each widget you create.
Traceback:
File "/home/mattia/develop/caritas/zaccheo/zaccheo-ui/app.py", line 46, in <module>
anagrafica_page()
File "/home/mattia/develop/caritas/zaccheo/zaccheo-ui/app/pages/anagrafica.py", line 13, in anagrafica_page
authenticator = stauth.Authenticate(
^^^^^^^^^^^^^^^^^^^^
File "/home/mattia/venvs/venv-zaccheo-ui/lib/python3.11/site-packages/streamlit_authenticator/authenticate/__init__.py", line 53, in __init__
self.cookie_handler = CookieHandler(cookie_name,
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mattia/venvs/venv-zaccheo-ui/lib/python3.11/site-packages/streamlit_authenticator/authenticate/cookie/__init__.py", line 39, in __init__
self.cookie_manager = stx.CookieManager()
^^^^^^^^^^^^^^^^^^^
File "/home/mattia/venvs/venv-zaccheo-ui/lib/python3.11/site-packages/extra_streamlit_components/CookieManager/__init__.py", line 22, in __init__
self.cookies = self.cookie_manager(method="getAll", key=key, default={})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mattia/venvs/venv-zaccheo-ui/lib/python3.11/site-packages/streamlit_option_menu/streamlit_callback.py", line 20, in wrapper_register_widget
return register_widget(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I looked at the source code and it seems to me that there is no way to pass an explicit key to the CookieManager instantiated within the Authenticate object, which results for the CookieManager to always use the default init key.
My code follows.
app.py
import yaml
from yaml.loader import SafeLoader
import streamlit as st
import streamlit_authenticator as stauth
from streamlit_option_menu import option_menu
from app.pages.anagrafica import anagrafica_page
if __name__ == "__main__":
with st.sidebar:
st.title("My app")
with open('./settings/users.yaml') as file:
config = yaml.load(file, Loader=SafeLoader)
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['pre-authorized']
)
authenticator.login()
if st.session_state["authentication_status"]:
with st.sidebar:
# authenticator.logout()
st.write(f'Welcome *{st.session_state["name"]}*')
page = option_menu(
menu_title="Menù",
options=["Anagrafica", "Tessere", "Scontrini", "Prodotti"],
icons=["people-fill", "card-text", "receipt", "tag-fill"],
)
if page == "Anagrafica":
anagrafica_page()
elif st.session_state["authentication_status"] is False:
st.error('Username/password is incorrect')
elif st.session_state["authentication_status"] is None:
st.warning('Please enter your username and password')
app.pages.anagrafica.py
import yaml
from yaml.loader import SafeLoader
import streamlit as st
import streamlit_authenticator as stauth
def anagrafica_page():
with open('./settings/users.yaml') as file:
config = yaml.load(file, Loader=SafeLoader)
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['pre-authorized'],
# key="anagrafica-auth"
)
authenticator.login()
st.title("Anagrafica")
Environment:
- python 3.11
- streamlit 1.33.0
- extra-streamlit-components 0.1.71
- streamlit-option-menu 0.3.12
Is there some sort of bug or am I missing something?
I think that it might be the path you're in. It could also be that there might be an init file where there shouldn't be.
Same problem here
Dear all, this issue will be resolved in the next release.
Same problem here
same problem, is there a fix?
On the way!
Same problem here
I'm not sure if this will help anyone or not, but in my app, I created a dedicated page for login, home page after login, etc... I only declared the streamlit authenticator object in the subpages and not in the "entrypoint" file (in this case, app.py). I did have authenticator.login() on any page that was used after the login in case of session refresh in addition to the additional login page. I mean to say if there are any other pages that are used before the user has logged in, the line authenticator.login() was not needed.
Same problem here
Fixed! Please refer to v0.3.3.
I am still having this issue on v0.3.3. I have an authenticated decorator which creates an Authenticate object but still gives this error upon instantiation.
Actually with the exact same code, it still gives the exact same error. In v0.3.3 @mkhorasani you introduced key but only to the widgets. However, the error is raised while instantiating the Authenticate class and the actual line where the duplicate widget issue arises is:
return register_widget_from_metadata(metadata, ctx, widget_func_name, element_type)
And when traced back, this is where it happens:
class CookieManager:
def __init__(self, key="init"):
self.cookie_manager = _component_func
self.cookies = self.cookie_manager(method="getAll", key=key, default={})
....
and where you instantiate the CookieManager class, you do not accept a key
class CookieModel:
"""
This class executes the logic for the cookies for password-less re-authentication,
including deleting, getting, and setting the cookie.
"""
def __init__(self, cookie_name: str, cookie_key: str, cookie_expiry_days: float):
...
self.cookie_manager = stx.CookieManager()
...
Hence, I think the bug problem is still there.
Actually with the exact same code, it still gives the exact same error. In v0.3.3 @mkhorasani you introduced key but only to the widgets. However, the error is raised while instantiating the
Authenticateclass and the actual line where the duplicate widget issue arises is:return register_widget_from_metadata(metadata, ctx, widget_func_name, element_type)And when traced back, this is where it happens:class CookieManager: def __init__(self, key="init"): self.cookie_manager = _component_func self.cookies = self.cookie_manager(method="getAll", key=key, default={}) ....and where you instantiate the
CookieManagerclass, you do not accept a keyclass CookieModel: """ This class executes the logic for the cookies for password-less re-authentication, including deleting, getting, and setting the cookie. """ def __init__(self, cookie_name: str, cookie_key: str, cookie_expiry_days: float): ... self.cookie_manager = stx.CookieManager() ...Hence, I think the bug problem is still there.
Hi @serkankalay instead of creating multiple Authenticate objects, can you please create it only once, save it to session state, and access it from the session state where ever you need to.
Hi @mkhorasani. That's actually what I did, and it works perfectly fine. For others maybe to make use of it:
- I have a decorator that creates and
Authenticateif it doesn't exist in thest.session_state
def authenticated(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if (
"authentication_status" not in st.session_state
or "my_auth_key" not in st.session_state
):
my_auth_key = str(uuid.uuid4())
st.session_state["my_auth_key"] = my_auth_key
else:
my_auth_key = st.session_state["my_auth_key"]
name, authentication_status, username = _get_authenticator(
my_auth_key
).login("main")
if authentication_status is False:
st.error("Username/password is incorrect")
elif authentication_status is None:
st.warning("Please enter your username and password")
elif authentication_status:
return func()
return wrapper
- I have another file that is not interacting with any streamlit component, where the
_get_authenticatoris defined:
from __future__ import annotations
import yaml
from streamlit_authenticator import Authenticate
from yaml.loader import SafeLoader
with open("./authentication_config.yaml") as file:
_CONFIG = yaml.load(file, Loader=SafeLoader)
_AUTHENTICATOR_MAPPING: dict[str, Authenticate] = {}
def _make_authenticator(auth_key: str) -> None:
if auth_key not in _AUTHENTICATOR_MAPPING:
_AUTHENTICATOR_MAPPING[auth_key] = Authenticate(
_CONFIG["credentials"],
_CONFIG["cookie"]["name"],
_CONFIG["cookie"]["key"],
_CONFIG["cookie"]["expiry_days"],
_CONFIG["preauthorized"],
)
def _get_authenticator(auth_key: str) -> Authenticate:
_make_authenticator(auth_key)
return _AUTHENTICATOR_MAPPING[auth_key]
- And on every page, I encapsulate all the page building in a function and decorate it with it
@authenticated
def _build() -> None:
st.header("Hello world")
# More awesome things built here
Hope this helps someone.
Hello @serkankalay,
I am a new Streamlit user and have been struggling with an issue. My app is a multipage application, and when I try to implement your strategy, My application consists of three forms, and each user's dropdowns should be populated with different data. However, what's happening is that the first user's session instance is affecting all other users' forms, it creates a single-user session instance that is shared across the server. As a result, the details of the first user who logs in are distributed to all other users, even though they have their own instances of the app and regardless of them logging in with their credentials.
This is my separate file auth.py, where I am loading credentials from a Google Sheet. I am also defining the decorator here and calling it in the root app entry file.
import streamlit as st
from streamlit_authenticator import Authenticate
from streamlit_gsheets import GSheetsConnection
from typing import Callable, Any
from functools import wraps
conn = st.connection("gsheets", type=GSheetsConnection)
users_df = conn.read(worksheet="Users")
users = users_df.to_dict("records")
credentials = {"usernames": {}}
for user in users:
credentials["usernames"][user["username"]] = {
"fullname": user["name"],
"name": user["username"],
"email": user["email"],
"password": user["password"],
"role": user["role"],
"Territory_ID": user["Territory_ID"],
}
authenticator_mapping = {}
# AUTH_SECRET_KEY is stored in secrets.toml
def create_authenticator(AUTH_SECRET_KEY: str) -> None:
if AUTH_SECRET_KEY not in authenticator_mapping:
authenticator_mapping[AUTH_SECRET_KEY] = Authenticate(
credentials=credentials,
cookie_name="my_app_cookie",
cookie_key=AUTH_SECRET_KEY,
cookie_expiry_days=7,
preauthorized=None,
)
def get_authenticator(AUTH_SECRET_KEY: str) -> Authenticate:
create_authenticator(AUTH_SECRET_KEY)
return authenticator_mapping[AUTH_SECRET_KEY]
# Decorator
def authenticated(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if "AUTH_SECRET_KEY" not in st.session_state:
st.session_state["AUTH_SECRET_KEY"] = AUTH_SECRET_KEY
AUTH_SECRET_KEY = st.session_state["AUTH_SECRET_KEY"]
authenticator = get_authenticator(AUTH_SECRET_KEY)
login_result = authenticator.login(location="main")
if login_result is None:
st.warning("Please enter your username and password.")
return None
auth_status = login_result
if auth_status:
return func(*args, **kwargs)
elif auth_status is False:
st.error("Invalid username or password")
else:
st.warning("Please log in to continue")
return wrapper
Any guidance on how to resolve this would be greatly appreciated.
Thank you!