TangleSpace/hydralit

making cookie based login work.

0-MegaMind-0 opened this issue ยท 2 comments

So I was trying to hold a JWT token in cookie when login button is pressed.

app.py

import hydralit as hy
import streamlit as st
from hydralit_components import CookieManager
from login import LoginApp

cookie_manager = CookieManager()
app = hy.HydraApp(title='test', favicon="๐Ÿ™", hide_streamlit_markers=True,
                  allow_url_nav=True, sidebar_state="expanded",
                  layout='wide'
                  )

app.add_app("Login", LoginApp(cookie_manager=cookie_manager), is_login=True, logout_label="Logout")

@app.logout_callback
def mylogout_cb():
    cookie_manager.delete('user_data')
    print('I was called from Hydralit at logout!')

@app.login_callback
def mylogin_cb():
    print('I was called from Hydralit at login!')

login.py

class LoginApp(HydraHeadApp):
    """
    This is an example login application to be used to secure access within a HydraApp streamlit application.
    This application implementation uses the allow_access session variable and uses the do_redirect method if the login check is successful.

    """

    def __init__(self, cookie_manager, title='', **kwargs):
        self.__dict__.update(kwargs)
        self.title = title
        self.cookie_manager = cookie_manager

    def _check_cookie_login(self) -> dict | None:
        """
        Check if the user is logged in.
        """
        session_cookie = self.cookie_manager.get("user_data")
        if session_cookie:
            return {'user': 'joe', 'level': 1}
        # ToDo: Check parse jwt and check if its valid and then return the user dict

    def run(self) -> None:
        """
        Application entry point.
        """
        login = self._check_cookie_login()
        if login:
            self.set_access(1, login['user'], cache_access=True)
            self.do_redirect()

        st.markdown("<h1 style='text-align: center;'>Secure Hydralit Login</h1>", unsafe_allow_html=True)

        c1, c2, c3, = st.columns([2, 2, 2])

        form_data = self._create_login_form(c2)

        pretty_btn = """
        <style>
        div[class="row-widget stButton"] > button {
            width: 100%;
        }
        </style>
        <br><br>
        """
        c2.markdown(pretty_btn, unsafe_allow_html=True)

        if form_data['submitted']:
            self._do_login(form_data, c2)

    @staticmethod
    def _create_login_form(parent_container) -> Dict:

        login_form = parent_container.form(key="login_form")

        form_state = {}
        form_state['username'] = login_form.text_input('Username')
        form_state['password'] = login_form.text_input('Password', type="password")
        form_state['submitted'] = login_form.form_submit_button('Login')

        parent_container.write("sample login -> joe & joe")

        return form_state

    def _do_login(self, form_data, msg_container) -> None:

        # access_level=0 Access denied!
        access_level = self._check_login(form_data)

        if access_level > 0:
            msg_container.success(f"โœ”๏ธ Login success")
            with st.spinner("๐Ÿค“ now redirecting to application...."):
                time.sleep(1)

                self.set_access(1, form_data['username'], cache_access=True)
                self.cookie_manager.set("user_data", 'True')
                # Do the kick to the home page
                self.do_redirect()
        else:
            self.session_state.allow_access = 0
            self.session_state.current_user = None

            msg_container.error(f"โŒ Login unsuccessful, ๐Ÿ˜• please check your username and password and try again.")

    def _check_login(self, login_data) -> int:

        if login_data['username'] == 'joe' and login_data['password'] == 'joe':
            return 1
        else:
            return 0

The above basically checks if there is a cookie with name user_data and logs in automatically if its set.
When the cookie is not found it loads the modified example from example code.

When the username and password matches. It should set a cookie with name user_data but this fails here.
But doing the same using login_callback in app.py works

@app.logout_callback
def mylogout_cb():
    cookie_manager.delete('user_data')
    print('I was called from Hydralit at logout!')

@app.login_callback
def mylogin_cb():
    print('I was called from Hydralit at login!')
    cookie_manager.set('user_data', "Joe")

Now whenever I reload the page I can skip the login page. But hitting the logout button sends invokes the mylogout_cb function. But cookie_manager.delete('user_data') has no effect and doesn't delete the cookie.

But putting the same cookie_manager.delete('user_data') inside the mylogin_cb callback actually deletes the cookie on next login / on page reload.

Am i missing something here ?. What would be the best way for me to achieve this. I tried extra_streamlit_components library and it has the same behaviour

Just gave https://github.com/ktosiek/streamlit-cookies-manager a try. everything works as expected. Cookies get saved when login button is pressed (code for saving the jwt cookie is in the LoginApp).
Cookies get deleted when logout_callback is invoked.

How are you able to achieve a logout button in your code? I am able to login but I get an error saying that an app named Logout doesn't exist. What could I do to rectify this error ?