Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New style multipage application refresh on a subpage routes back to default when authenticator is implemented #179

Open
ShaunHide17 opened this issue Jul 29, 2024 · 14 comments
Labels
help wanted Extra attention is needed

Comments

@ShaunHide17
Copy link

Hi,

I am using the latest version of streamlit-authenticator (0.3.3) with the latest version of streamlit (1.37.0)

I am using the new multipage routing mechanism that streamlit recently released:
https://docs.streamlit.io/develop/concepts/multipage-apps/page-and-navigation

If I navigate away from the default page (View) to View2 then do a hard refresh (i.e. F5), it refreshes back to the default page (View) rather than the page I refreshed on. This behaviour does not occur without the instantiate_authenticator() function call so cant seem to work out what is driving the return to default behaviour

Can you offer any guidance?

Here is a sample of the main file from which the application is started with streamlit run

import streamlit as st
from helpers.authentication import instantiate_authenticator

st.set_page_config(page_title="Home", page_icon="", layout="wide")

instantiate_authenticator()
if st.session_state["authentication_status"]:

  view = st.Page("pages/view.py", title="View", default=True)
  view2 = st.Page("pages/view2.py", title="View2")

  navigation_dictionary = {
    "Views": [view, view2]
  }
  pg = st.navigation(navigation_dictionary)
  pg.run()

With the instantiate_authenticator() function being:

import os

import yaml
from yaml.loader import SafeLoader

import streamlit as st
import streamlit_authenticator as auth

def instantiate_authenticator():

    ## Load in the configuration from the environment variable
    config = yaml.load(os.environ["CONFIG"], Loader=SafeLoader)

    ## NOTE: Both the authenticator and the login must be triggered on every page.
    ##  Pushing the authenticator to st.session_state is to allow the user to logout

    ## Authenticator
    authenticator = auth.Authenticate(
        credentials=config["credentials"],
        cookie_name=config["cookie"]["name"],
        cookie_key=config["cookie"]["key"],
        cookie_expiry_days=config["cookie"]["expiry_days"],
        pre_authorized=config["pre-authorized"],
    )
    authenticator.login()

    if "authenticator" not in st.session_state:
        st.session_state.authenticator = authenticator

    if st.session_state["authentication_status"] == False:
        st.error("Username/password is incorrect")

@mkhorasani mkhorasani added the help wanted Extra attention is needed label Jul 29, 2024
@mkhorasani
Copy link
Owner

Hi @ShaunHide17, thank you for reaching out. Instead of using this custom function, can you try to add an authenticator.login(location='unrendered') widget to each other page to see if it will solve your problem?

@ShaunHide17
Copy link
Author

Hi @mkhorasani , thanks for the quick reply and the help!

Sadly adding in the authenticator.login(location='unrendered') into each of the headers generates an error (below)

I think the new routing causes a multipage visit on the first streamlit run call and in doing so generates a DupliecateWidgetID error:
DuplicateWidgetID(streamlit.errors.DuplicateWidgetID: There are multiple widgets with the same `key='init'`.

I supply the key parameter to authenticator.login() with it being different for each of the pages, but it seems to error out further upstream in the stx.CookieManager() call

Full error log below:

Traceback (most recent call last):
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/exec_code.py", line 75, in exec_func_with_error_handling
    result = func()
             ^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/runtime/scriptrunner/script_runner.py", line 574, in code_to_exec
    exec(code, module.__dict__)
  File "/Users/shaunhide/Desktop/st_example/example.py", line 29, in <module>
    pg.run()
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/navigation/page.py", line 291, in run
    exec(code, module.__dict__)
  File "/Users/shaunhide/Desktop/st_example/views/view.py", line 5, in <module>
    instantiate_authenticator(key="view")
  File "/Users/shaunhide/Desktop/st_example/helpers/authentication.py", line 22, in instantiate_authenticator
    authenticator = auth.Authenticate(
                    ^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit_authenticator/views/authentication_view.py", line 51, in __init__
    self.cookie_controller  =   CookieController(cookie_name,
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit_authenticator/controllers/cookie_controller.py", line 27, in __init__
    self.cookie_model = CookieModel(cookie_name,
                        ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit_authenticator/models/cookie_model.py", line 40, in __init__
    self.cookie_manager         =   stx.CookieManager()
                                    ^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/extra_streamlit_components/CookieManager/__init__.py", line 22, in __init__
    self.cookies = self.cookie_manager(method="getAll", key=key, default={})
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/components/v1/custom_component.py", line 58, in __call__
    return self.create_instance(
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/runtime/metrics_util.py", line 408, in wrapped_func
    result = non_optional_func(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/components/v1/custom_component.py", line 228, in create_instance
    return_value = marshall_component(dg, element)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/components/v1/custom_component.py", line 202, in marshall_component
    component_state = register_widget(
                      ^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/runtime/state/widgets.py", line 169, in register_widget
    return register_widget_from_metadata(metadata, ctx, widget_func_name, element_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/shaunhide/Desktop/st_example/.venv/lib/python3.12/site-packages/streamlit/runtime/state/widgets.py", line 202, in register_widget_from_metadata
    raise DuplicateWidgetID(
streamlit.errors.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.

@mkhorasani
Copy link
Owner

mkhorasani commented Jul 31, 2024

It seems that you are recreating several stauth.Authenticate objects, probably one for each page. If this is indeed the case, you will face a DuplicateWidgetID error since the cookie manager does not use a unique key for each object. Instead, you should save the authenticator object to the session state once on your home page and access it from the session state from other pages.

@smboy
Copy link

smboy commented Aug 13, 2024

can you help me understand a little bit here. when authenticator object is saved to the session state, and when user refreshes the page, wouldn't it loose the state? st.session_state is not persisted when page refreshes. So we loose the state and forces user to login. I'm having a similar issue in my app.

@mkhorasani
Copy link
Owner

mkhorasani commented Aug 13, 2024

Hi @smboy, session_state will only be lost if the user does a hard refresh. A normal refresh will not remove the session_state. But even if the user does a hard refresh the re-authentication cookie will log them in automatically. I hope this answers your question.

@smboy
Copy link

smboy commented Aug 16, 2024

Thanks for the details. Couple of followup questions:

  • Just to clarify, this works seamlessly for a multipage application?
  • also multi-user application without any issues?
  • does this work across browsers too? seen case where Safari was not storing the cookie and when I refreshed (simple refresh) the page, it forced me to login.

Thanks again!

@mkhorasani
Copy link
Owner

Thanks for the details. Couple of followup questions:

  • Just to clarify, this works seamlessly for a multipage application?
  • also multi-user application without any issues?
  • does this work across browsers too? seen case where Safari was not storing the cookie and when I refreshed (simple refresh) the page, it forced me to login.

Thanks again!

Hi @smboy, can you verify that the issue you had was resolved after adding the authenticate object to session state? As for your questions, yes, yes, and yes. I've had no problems on my end.

@smboy
Copy link

smboy commented Aug 16, 2024

It seems to be working on a standalone code. I will add it to my application and will update you (I was using a different authentication module which was having similar issues of persistence during page refresh).

meanwhile, anyway to customize the login? I would like to only display email and pwd..username is not required for my application. Thanks much again for your quick response!

@mkhorasani
Copy link
Owner

Yes, as of the latest version, users can now register their email as their username too. Feel free to change the name of the username field to email if you wish, you can do so using the fields={'Username':'Email'} argument.

@consoleLog7
Copy link

Yes, as of the latest version, users can now register their email as their username too. Feel free to change the name of the username field to email if you wish, you can do so using the fields={'Username':'Email'} argument.

@mkhorasani this is a great new feature! I am trying to keep things simple for my users and just have email and password, so this helps.

I have this working on the login widget, however, can't get it working on register_user. When running authenticator.register_user(pre_authorization=True, fields={'Username':'Email'}) I get errors "Missing submit button" and "DuplicateWidgetID" from line 347 in register_user 'Username' if 'Username' not in fields. Not sure if this is jusr a me issue, or a feature request!

@mkhorasani
Copy link
Owner

Yes, as of the latest version, users can now register their email as their username too. Feel free to change the name of the username field to email if you wish, you can do so using the fields={'Username':'Email'} argument.

@mkhorasani this is a great new feature! I am trying to keep things simple for my users and just have email and password, so this helps.

I have this working on the login widget, however, can't get it working on register_user. When running authenticator.register_user(pre_authorization=True, fields={'Username':'Email'}) I get errors "Missing submit button" and "DuplicateWidgetID" from line 347 in register_user 'Username' if 'Username' not in fields. Not sure if this is jusr a me issue, or a feature request!

Since the register_user widget has both username and email fields in the form, if you rename the username field to email you will indeed get a DuplicatWidgetID error since both text fields now have the same name. I will try to modify this widget in the next release to remove the username field if the user wants to combine both fields into one. Please stay tuned!

@smboy
Copy link

smboy commented Aug 21, 2024

If I may suggest, what would be appropriate is to take input fields as config parameter for registration and login. For instance, for registration, I want to use {user_email, pwd, company_name} and for login {username, password}.

In fact, what I was thinking is to even isolate further. If there is any clean way where I can come up with my own login screen and simply pass the values to your application, that would be even ideal. That way, we are separating the front end with that of the backend. Its upto user on what and how they customize the login screen with. Currently its tightly coupled with the login screen.

@mkhorasani
Copy link
Owner

If I may suggest, what would be appropriate is to take input fields as config parameter for registration and login. For instance, for registration, I want to use {user_email, pwd, company_name} and for login {username, password}.

In fact, what I was thinking is to even isolate further. If there is any clean way where I can come up with my own login screen and simply pass the values to your application, that would be even ideal. That way, we are separating the front end with that of the backend. Its upto user on what and how they customize the login screen with. Currently its tightly coupled with the login screen.

I definitely see a use case for this, but it would require major refactoring of the codebase and that is something that I can only commit to for the long term.

@christian-heins-velero
Copy link

christian-heins-velero commented Oct 30, 2024

i am still having the case where my page reloads to the default page, i have updated to the last version, provided authentication on the entry file, added the authenticator = st.session_state.authenticator and authenticator.login("unrendered") to every page, my app still reloads to default or main page

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

5 participants