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

Refreshing page does not use cookie to re-authenticate #149

Open
yhavin opened this issue Mar 27, 2024 · 13 comments
Open

Refreshing page does not use cookie to re-authenticate #149

yhavin opened this issue Mar 27, 2024 · 13 comments
Labels
help wanted Extra attention is needed

Comments

@yhavin
Copy link

yhavin commented Mar 27, 2024

Hello,

I have a multipage app and am having re-authentication problems when refreshing pages. I have updated to 0.3.2, but this problem occurred before and still remains.

Problem 1: refreshing on same page as authentication occurs

I log in on the home.py page (entry point), and that works fine. I can see the cookie gets stored in the browser devtools. If I refresh the page, I can see the st.session_state start off initially blank for a fraction of a second, then populate with the username, logout, and authentication_status attributes all set to NULL. I can see, however, that the init attribute contains the cookie key-value pair, so at least I can use that. Should the authentication_status and username attributes be NULL? I've been using custom logic that checks if the cookie key-value pair exists inside st.session_state.init, and if it does, to count the user as "logged in", but it seems like extra work. Intuitively, I'd expect the authentication_status and username attributes to use the cookie and have proper values.

Problem 2: refreshing on another page

After logging in on home.py entry point page (and whether I refresh that page or not), if I navigate to another page, say report.py, I can see the authentication_status and username attributes are correctly set. So I can easily use those to determine authentication status for my application. But if I then refresh while on report.py, the session state becomes a completely blank dictionary, not even including the cookie key-value pair. So now, how is my application meant to know if the user is logged in or not? I can see the cookie in the browser devtools, but not in st.session_state.init, as st.session_state is empty.

What is the recommended way to handle authentication status across multiple pages and taking into account reloads? Reloads are actually very prevalent, especially if someone wants to bookmark a specific page of my application. Whenever they open that bookmark, it is akin to a reload, and the st.session_state is empty and my application doesn't know if they're logged in.

Thanks in advance!

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

mkhorasani commented Mar 28, 2024

Dear @yhavin, if you are using Streamlit-Authenticator with multi-page apps, you will have to recreate the authenticator object on each and every page and invoke the login method as shown below:

authenticator = stauth.Authenticate(
    config['credentials'],
    config['cookie']['name'],
    config['cookie']['key'],
    config['cookie']['expiry_days'],
    config['pre-authorized']
)

authenticator.login()

This is to ensure that when a user hard refreshes the page and the session state variables related to re-authentication are lost, the authenticator object is there to re-initialize them from the cookie saved on the browser. Please let me know if this solves your issues and I will close this issue.

@yhavin
Copy link
Author

yhavin commented Mar 28, 2024

Thank you, that does solve the problem. However, is there a way to make the login widget unrendered? I'd prefer that if the user navigates to a report page and they are not logged in, for them to see a warning with a button that redirects them to the home page which has the login widget. Currently, however, if I'm calling authenticator.login() on each page, it shows the login widget directly on that page. It's not a bug, it's just a UI preference for me.

@mkhorasani
Copy link
Owner

@yhavin by default the login function will always check to see if there is a valid re-authentication cookie available on the browser, if there is it will log in without rendering the login form. However, in the event that a user refers to a subpage without ever having logged in, perhaps you can use the following code to redirect them to the main login page:

if 'authentication_status' not in st.session_state:
   st.warning('Please log in')
   if st.button("Login"):
       st.switch_page("login.py")

@yhavin
Copy link
Author

yhavin commented Mar 29, 2024

I have something similar to that already, however there are still issues.

Logically, I need to put the authenticator.login() function before checking if they're logged in (if "authentication_status" not in st.session_state), because authenticator.login() is the thing that re-authenticates if there is a cookie. Otherwise, the logged in check is not logically valid.

When the user is logged in, this is fine, because the login widget doesn't render and the page loads its contents normally.

But if the user is not logged in, then the login widget still shows up on the page because I have called authenticator.login(). For my UI, I don't want it to show up; I just want the warning and switch page suggestion.

So it's a bit of a catch 22. If I check for logged in status before calling authenticator.login(), then it won't re-authenticate using the cookie. And if I check for logged in status after calling authenticator.login(), then when they are not logged in, it shows the login widget, even though this doesn't make UX sense because I am prompting them to switch to the login (home) page.

Hence my question of whether I can make the login widget unrendered. That way, I can use it to check for re-authentication without it actually showing a login widget.

Please let me know if you understand this problem, and if you have a suggestion.

@mkhorasani
Copy link
Owner

Gotcha! Sure, will try to fix this in the next release. Thank you for bringing it to my attention.

@yhavin
Copy link
Author

yhavin commented Mar 29, 2024

Thank you, I really appreciate your responsiveness! It gives me peace of mind in relying on this package for my work.

@LeeOSO
Copy link

LeeOSO commented May 6, 2024

@mkhorasani HI, I have a problem here, I have two separate pages: login, webui. Login is the default page that requires authorization to log in. The code logic is as follows:

authenticator = stauth.Authenticate(
    config['credentials'],
    config['cookie']['name'],
    config['cookie']['key'],
    config['cookie']['expiry_days'],
    config['preauthorized']
name, authentication_status, username = authenticator.login()
if st.session_state["authentication_status"]:
  switch_page("webui")

After the login is successful, enter the webui page, the code is as follows:

 authenticator = stauth.Authenticate(
        config['credentials'],
        config['cookie']['name'],
        config['cookie']['key'],
        config['cookie']['expiry_days'],
        config['preauthorized']
    )
    name, authentication_status, username = authenticator.login()
    print(f'webui->authentication_status={authentication_status}, name={name}, username={username}')
    if not st.session_state["authentication_status"]:
        switch_page("login")

The above code can be successfully jumped to the webui page after the first login. But when I refresh on the webui page, the page was switched to the login page. Obviously, the page is lost the login status if refreshed.
Please help me see if I am using it correctly and what is causing the problem.


Another problem was discovered during the test. I successfully logged in as a user and clicked the logout button to switch to the login page. When I refreshed it, it jumped to the webui page again. It means that this situation means that the login status cannot be cleared after logging out.

@qiang-yu
Copy link

qiang-yu commented May 8, 2024

See my issue #159

Just code two lines would fix this

@mkhorasani
Copy link
Owner

See my issue #159

Just code two lines would fix this

Please, note that both lines are already implemented in the current version of Streamlit-Authenticator.

@LeeOSO
Copy link

LeeOSO commented May 9, 2024

See my issue #159
Just code two lines would fix this

Please, note that both lines are already implemented in the current version of Streamlit-Authenticator.

In fact this change I see is already in the current version, but there are still the problems mentioned above.

@qiang-yu
Copy link

qiang-yu commented May 9, 2024

See my issue #159
Just code two lines would fix this

Please, note that both lines are already implemented in the current version of Streamlit-Authenticator.

In fact this change I see is already in the current version, but there are still the problems mentioned above.

I just follow the document pip install and get 0.3.2 version , which has not implement these code

after fix it , Rresh page works perfect in 0.3.2

So, the current version is not pip install ? but install from github master ?

@mkhorasani
Copy link
Owner

mkhorasani commented May 9, 2024

See my issue #159
Just code two lines would fix this

Please, note that both lines are already implemented in the current version of Streamlit-Authenticator.

In fact this change I see is already in the current version, but there are still the problems mentioned above.

I just follow the document pip install and get 0.3.2 version , which has not implement these code

after fix it , Rresh page works perfect in 0.3.2

So, the current version is not pip install ? but install from github master ?

I just checked v0.3.2 again, and I can guarantee that your fixes are already implemented.

@qiang-yu
Copy link

authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['preauthorized']
)
name, authentication_status, username = authenticator.login()

I Confirm, the code is there

The issue i met is that , i saved authenticator in self.authenticator

self.authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['preauthorized']
)

on each page, i only call login use the saved variable

on each page, just call self.authenticator.login()
name, authentication_status, username = self.authenticator.login()

So, the correct way is , DO Not Save Authenticator For Future Use,
Re-Instantiate Authenticator every time, and use this instance to login

I saved the authenticator , and use it in every page, so i need my fix to make it work

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

4 participants