mstaal / msal_streamlit_authentication

MSAL Streamlit Component
MIT License
85 stars 21 forks source link

Oauth in multiple pages app #10

Open lionpeloux opened 12 months ago

lionpeloux commented 12 months ago

Hi @mstaal

I am having a hard time to make your component works in a multiple pages app. I want to have a landing page with the ability to login / logout. I want to check in each page if the token is valid and then give acces to its content.

It is not clear to me how to proceed (I've done several trial & errors). Would you mind giving me some hints ? I don't know If I need to add your component to each page.

Many thanks,

lionpeloux commented 12 months ago

This is the code that I put on each page (bellow). It seems that when I switch the page, a rerun happens twice. I go first to state "login_token = None" followed by "Login success". This does not seem to happen when I just rerun the page

Any Idea how to prevent the double rerun when switching pages ?

def OAuthButton(key=1):

    tenant_id:str =  st.secrets.credentials.tenant_id
    client_id:str = st.secrets.credentials.client_id
    client_secret:str = st.secrets.credentials.client_secret
    scopes = st.secrets.credentials.scopes
    redirect_url = st.secrets.credentials.redirect_url
    credentials = (client_id, client_secret) 

    with st.sidebar:

        login_token = msal_authentication(
            auth={
                "clientId": client_id,
                "authority": f"https://login.microsoftonline.com/{tenant_id}",
                "redirectUri": f"{redirect_url}",  # Modify this if your app's redirect URI is different
                "postLogoutRedirectUri": "/"  # Modify this if your app's post logout redirect URI is different
            },
            cache={
                "cacheLocation": "sessionStorage", # https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/caching.md
                "storeAuthStateInCookie": False
            },
            login_request={
                # f"{client_id}/.default", 
                "scopes": scopes
            },
            logout_request={},  # Modify this if you need to specify a logout request
            login_button_text="Login",  # Optional, defaults to "Login"
            logout_button_text="Logout",  # Optional, defaults to "Logout"
            class_name="css_button_class_selector",  # Optional, defaults to None. Corresponds to HTML class.
            html_id="html_id_for_button",  # Optional, defaults to None. Corresponds to HTML id.
            key=key  # Optional if only a single instance is needed
        )

    if login_token != None:

        st.success("Login success")

        username = login_token["account"]["username"]
        st.sidebar.write(f"Welcome {username}")

        # instanciate a 0365-python account to manipulate mailbox
        login_token['refresh_token'] = '' # this is requiered as it is not defined by msal_streamlit_authentication
        token_backend.token = login_token
        account = Account(credentials, token_backend=token_backend)
        if account.is_authenticated:
            st.success("Authentification success")
            st.write()
        else:
            st.error("Authentification failure")
    else:
        st.error("Login failure : please login ...")
mstaal commented 12 months ago

Hi @lionpeloux !

I apologize for your troubles. While I am not 100% certain why you encounter these issues, I would guess it's because of how Streamlit is architected. I think the issue is that if the button is not present in the current UI / page, the login_token is `None´, and thereby of zero value to you. Alternatively, it could be a matter of how Streamlit handles state. My understanding is that Streamlit treats each component as a separate iframe (a web concept) with its own state.

I have a few ideas, both for the long and short term. I know Streamlit has a concept called Session State, which should basically allow you to put whatever data you want into a "session store" (essentially a globally available dictionary). And I would probably insert the generated token into the session store and add some validation logic to check that the token is still valid.

I am also considering adding some adjustment to the library, so that it can silently fetch a token, if you are already logged in (https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-acquire-token?tabs=javascript2), but I do not know if that would fix your issue.

I hope you resolve your issue, and otherwise I will try to give it one further shot. Do you have a public repo with the code, so that I can run it locally?

ameen7626 commented 4 months ago

This is the code that I put on each page (bellow). It seems that when I switch the page, a rerun happens twice. I go first to state "login_token = None" followed by "Login success". This does not seem to happen when I just rerun the page

Any Idea how to prevent the double rerun when switching pages ?

def OAuthButton(key=1):

    tenant_id:str =  st.secrets.credentials.tenant_id
    client_id:str = st.secrets.credentials.client_id
    client_secret:str = st.secrets.credentials.client_secret
    scopes = st.secrets.credentials.scopes
    redirect_url = st.secrets.credentials.redirect_url
    credentials = (client_id, client_secret) 

    with st.sidebar:

        login_token = msal_authentication(
            auth={
                "clientId": client_id,
                "authority": f"https://login.microsoftonline.com/{tenant_id}",
                "redirectUri": f"{redirect_url}",  # Modify this if your app's redirect URI is different
                "postLogoutRedirectUri": "/"  # Modify this if your app's post logout redirect URI is different
            },
            cache={
                "cacheLocation": "sessionStorage", # https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/caching.md
                "storeAuthStateInCookie": False
            },
            login_request={
                # f"{client_id}/.default", 
                "scopes": scopes
            },
            logout_request={},  # Modify this if you need to specify a logout request
            login_button_text="Login",  # Optional, defaults to "Login"
            logout_button_text="Logout",  # Optional, defaults to "Logout"
            class_name="css_button_class_selector",  # Optional, defaults to None. Corresponds to HTML class.
            html_id="html_id_for_button",  # Optional, defaults to None. Corresponds to HTML id.
            key=key  # Optional if only a single instance is needed
        )

    if login_token != None:

        st.success("Login success")

        username = login_token["account"]["username"]
        st.sidebar.write(f"Welcome {username}")

        # instanciate a 0365-python account to manipulate mailbox
        login_token['refresh_token'] = '' # this is requiered as it is not defined by msal_streamlit_authentication
        token_backend.token = login_token
        account = Account(credentials, token_backend=token_backend)
        if account.is_authenticated:
            st.success("Authentification success")
            st.write()
        else:
            st.error("Authentification failure")
    else:
        st.error("Login failure : please login ...")

Hi @lionpeloux, Did you find any fix for this issue