bleumink / streamlit-keycloak

User authentication and single sign-on in your Streamlit app using Keycloak
MIT License
52 stars 8 forks source link

Multipage app #20

Open prki opened 10 months ago

prki commented 10 months ago

Hey, not sure if this is an issue or rather a support topic, but I have not been able to find anything anywhere so I figured out this might be the best place to ask.

TL;DR - How do I use streamlit-keycloak with a multi-page app?

Detailed Q: When doing the example flow, such as:

keycloak = login(...)

if keycloak.authenticated:
    render_page()
else:
    display_not_auth()

This does work, but with two significant caveats:

  1. login() seems to be async under the hood. Even though the Python function is synchronous, it appears the behavior is that once the data is fetched, the page is re-rendered - with the data being populated, somehow.
    • That is fine for first-page entry because authenticated == false until keycloak resolves this. However, I noticed strange behavior where selecting a particular page results in double re-rendering - it appears that keycloak is already considered authenticated, retrieving some base value (what has already been set?), keycloak.authenticated is resolved as true and then new token is retrieved. On refresh, this behaves on default. I do not understand Streamlit's inner working well to understand where this data is stored.
  2. login() is called each time on every single page. Every single page gets a new token, which it does not need to get - and the token could just be reused.

As such, is there any recommended approach how to use this component for multi-page apps? I have a few ideas, but they feel like bending the component a bit - for instance having a separate class managing the token and populating the value and the class being in st.session_state - unsure if that would even work. Or if there is a callback that can be passed as a parameter which would be used to populate the resulting keycloak dataclass into the session state and then use that.

Is there a recommended approach?

bleumink commented 10 months ago

Hi Marek,

I haven't considered multi-page apps when developing the component. What I've seen is that Streamlit rerenders the app whenever data is send from the frontend to the backend. So this would happen when logging in and refreshing the token. I think your first idea of using st.session_state to manage the token will probably work, but will probably also involve some clever hacks.

A while back I started developing a more generic component that would work for all OIDC providers, not just Keycloak, and which leveraged st.session_state so would work in multipage apps as well. Here I ran into some issues getting the page refreshing and data persistance right, stranding the effort for now, so things might be more complicated than they seem.

I haven't had a lot of personal time to work on this, so it might take a while before I have another look at this. I wish I could be of more help.

prki commented 10 months ago

No worries - one idea I've had was to register callbacks in init_options() and rewrite them in my own token manager that would be in st.session_state, the idea being that the token manager class would be a wrapper over Keycloak dataclass.

If I were to do that, I have a question going toward your expertise - when exactly are these callbacks executed? Or more specifically, I am very clearly able to specify a callback to e.g. onAuthSuccess event, as stated by keycloak-js - I tested this and it is indeed called (though I am not 100% confident it's executed at that point because I didn't have much time to figure out the details under the hood). If I however do that, would the library work? Or rather, wouldn't the callbacks to these events override the behavior of your component? In other words... would it even work? :)

Don't worry about not being too much help - in all honesty the multiple token requests are acceptable, the double rendering is however a problem since the application I am working on runs API requests against different software - in which case the double re-render has potential to execute multiple requests on the same device. Fortunately they are all idempotent, but they do introduce significant calculation stress on the API-providing machine.

That being said, I would like to ask you actually about the double re-render in general - I have low level of knowledge when it comes to streamlit custom components, but the way I understand streamlit is that:

Now the behavior that confuses me is:

I very much appreciate your assistance here!

perrauer commented 2 months ago

Did this issue get a resolution?

I see the same behavior and was thinking of validating the jwt-tokens cached for the session as a workaround.

talrejanikhil commented 3 weeks ago

No worries - one idea I've had was to register callbacks in init_options() and rewrite them in my own token manager that would be in st.session_state, the idea being that the token manager class would be a wrapper over Keycloak dataclass.

If I were to do that, I have a question going toward your expertise - when exactly are these callbacks executed? Or more specifically, I am very clearly able to specify a callback to e.g. onAuthSuccess event, as stated by keycloak-js - I tested this and it is indeed called (though I am not 100% confident it's executed at that point because I didn't have much time to figure out the details under the hood). If I however do that, would the library work? Or rather, wouldn't the callbacks to these events override the behavior of your component? In other words... would it even work? :)

Don't worry about not being too much help - in all honesty the multiple token requests are acceptable, the double rendering is however a problem since the application I am working on runs API requests against different software - in which case the double re-render has potential to execute multiple requests on the same device. Fortunately they are all idempotent, but they do introduce significant calculation stress on the API-providing machine.

That being said, I would like to ask you actually about the double re-render in general - I have low level of knowledge when it comes to streamlit custom components, but the way I understand streamlit is that:

  • User clicks on page "MyPage", this is represented by a script pages/1_MyPage.py
  • Streamlit engine executes the script, all st.x calls get used to collect data that the streamlit engine renders.

Now the behavior that confuses me is:

  • My page uses your component in the common flow. Meaning that the component is defined in the script as keycloak = login(...). The name keycloak is scoped only within that script and within that execution.
  • I click on another page, then the previous page - script gets executed from start to end. keycloak = login(...) gets called. Why does it pass as keycloak.authenticated == true in the first place? Is the keycloak instance somehow a singleton under the hood and in case it exists in the component, by default the previous values are already provided? => If so, I would have to think about the implications, but a "naive solution" would be to always return an empty/uninitialized keycloak dataclass. Maybe.

I very much appreciate your assistance here!

how did you add callbacks to the streamlit-keycloak library?