mstaal / msal_streamlit_authentication

MSAL Streamlit Component
MIT License
95 stars 21 forks source link

Function not returning login Token #9

Open gcassimi opened 1 year ago

gcassimi commented 1 year ago

Hello! Great work with the lib, it is really helpful.

I am having some problems in the login function. It is not returning the correct token, and therefore not opening up my app. The logic I am using for storing the session state works, but for some reason after I go through the whole process of login the token is not returned.

Do you know what could be causing this issue?

This is the function I built:

def login(): if "logged_in" not in st.session_state: CLIENT_ID = os.getenv("CLIENT_ID") CLIENT_SECRET = os.getenv("CLIENT_SECRET") TENANT_ID = os.getenv("TENANT_ID")

    login_token = None
    # Setup MSAL authentication
    login_token = msal_authentication(
        auth={
            "clientId": CLIENT_ID,
            "authority": f"https://login.microsoftonline.com/{TENANT_ID}",
            "redirectUri": "",  # 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",
            "storeAuthStateInCookie": False
        },
        login_request={
            "scopes": [f"{CLIENT_ID}/.default"]
        },
        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=1  # Optional if only a single instance is needed
    )
    print(login_token)

    if login_token is not None:
        st.session_state.logged_in = True
        st.session_state.login_token = login_token
lionpeloux commented 1 year ago

Hi Mstaal,

Same here, I tried to make this work for hours now ...

I've register an application on Azure portal with :

I managed to have the login popup showing after clicking you button, going trough the oauth process (email + password) in that popup, but when it returns to my streamlit app (at http://localhost:8501/) the token is always None.

Can you share some tips to sort this out ? My goal is to get access to O365 (python-O365) from within my streamlit app and read some emails and calendar events. Thanks !

import streamlit as st
from msal_streamlit_authentication import msal_authentication

tenant_id:str =  "****"
client_id:str = "****"
client_secret:str = "****"

login_token = None

# Setup MSAL authentication
login_token = msal_authentication(
    auth={
        "clientId": client_id,
        "authority": f"https://login.microsoftonline.com/{tenant_id}",
        "redirectUri": "http://localhost:8501",  # 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",
        "storeAuthStateInCookie": False
    },
    login_request={
        "scopes": [f"{client_id}/.default"]
    },
    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=1  # Optional if only a single instance is needed
)
print(login_token)
st.write("Recevied login token:", login_token)
lionpeloux commented 1 year ago

Ok, I finally find the answer on st's forum : https://discuss.streamlit.io/t/adding-azure-active-directory-sign-in-sign-out-using-msal-python-handling-redirects/37032/9?u=lionpeloux

mstaal commented 1 year ago

@lionpeloux : Yes, I have read many claim they need to register the app accordingly in AD. Does it all work now?

@gcassimi : Have you tried to see if this also resolves your issue? Or have you managed to get it to work?

lionpeloux commented 1 year ago

Hi @mstaal, thanks for getting back to me.

Yes it is working now, since I've registered a SPA application in AAD according to the forum's thread I've pointed to.

However, it is not clear to me what should be in a standard MSAL acces_token. If I inspect what's in your login_token I get :

{
"authority":"https://login.microsoftonline.com/1e71a412-cdf9-.../"
"uniqueId":"f809778d-598b-..."
"tenantId":"1e71a412-cdf9-f36df6e5c428"
"scopes":[...]
"account":{...}
"idToken":"eyJ0eXAiOiJKV1QiL..."
"idTokenClaims":{...}
"accessToken":"eyJ0eXAiOiJKV1QiLCJub..."
"fromCache":false
"expiresOn":"2023-06-28T10:20:45.000Z"
"correlationId":"ab4a1746-..."
"requestId":"2f5bd415-..."
"extExpiresOn":"2023-06-28T11:29:05.000Z"
"familyId":""
"tokenType":"Bearer"
"state":""
"cloudGraphHostName":""
"msGraphHost":""
"fromNativeBroker":false
}

My goal is to use streamlit + python-0365 to make an app that builds complex standardized emails. python-0365 can work with a pre-existing token (see TokenBackend) but it is not clear to me in what shema/shape this token is expected.

It seems it is a python dict with not only the access_token field but some more informations, scopesfor instance.

Also, can you expose the refresh_token in your login_token?

lionpeloux commented 1 year ago

This is the token I got when I'm authenitifcating with the python-O365 console flow :

{
 "token_type": "Bearer",
 "scope": [
  "profile",
  "openid",
  "email",
  "https://graph.microsoft.com/Calendars.ReadWrite",
  "https://graph.microsoft.com/Calendars.ReadWrite.Shared",
  "https://graph.microsoft.com/Contacts.ReadWrite",
  "https://graph.microsoft.com/Contacts.ReadWrite.Shared",
  "https://graph.microsoft.com/Mail.ReadWrite",
  "https://graph.microsoft.com/Mail.ReadWrite.Shared",
  "https://graph.microsoft.com/Mail.Send",
  "https://graph.microsoft.com/Mail.Send.Shared",
  "https://graph.microsoft.com/User.Read"
 ],
 "expires_in": 3724,
 "ext_expires_in": 3724,
 "access_token": "eyJ0eXAiOiJKV1...",
 "refresh_token": "0.AS8AEqRxHvnN9k...",
 "expires_at": 1687728646.671638
}
lionpeloux commented 1 year ago

Ok, I finally made it by providing a token with an empty refresh token string :

token = {
        "token_type": login_token["tokenType"],
        "scope": login_token["scopes"],
        "access_token": login_token["accessToken"],
        "refresh_token": "", # requiered by python-O365
    }
mstaal commented 1 year ago

Hi @lionpeloux, Sorry for reaching back so late. I am glad that you got it to work! I have been a little busy. I am not sure if I can expose the refresh token, but I can look into it. I would imagine, though, that it is in a sense "outside the scope" of the MSAL library. The purpose of a refresh token is to securely allow for an OAuth 2 / Open ID Connect session to be kept alive, and MSAL should take care of that for itself. In other words, I do not think you would like to extend the refresh token to some external vendor.

From my point of view, the purpose of the token received to you as either a simple authentication mechanism for streamlit to "know who is logged in", or you can use it to pass the token over to (for instance) a backend of your choice that you tell to trust tokens issued by the token provider. Does that make sense?

I will look int the library you use. It looks interesting!