Get Active Directory authentification data

Hi,

I am deploying a streamlit app as an azure webapp to azure.
I succesfully configured Active Directory to handle user sign-in and then redirect to my app.
Now I would like to access the name of the user that signed in to store it together with the entered data of the user in a database.

The method that I found to get the user name is to run this javascript snippet in my browser console:

fetch("/.auth/me")
.then(function(response) {
return response.json().then(body => console.log(body));
})

However, I am unsure how I could access this data on the python side where the database entries are made.
Do I need to make a small custom component for this which runs the snippet and sends it to the python backend?

EDIT: theoretically I could make that rest API call also on the server side directly if I could access the authentification token of the user session, but I could only find an open thread about how to access request headers: Pass user HTTP request headers along to Python · Issue #1083 · streamlit/streamlit · GitHub

EDIT EDIT: I found this: Allow arbitrary Javascript code blocks (e.g. Google Analytics) · Issue #969 · streamlit/streamlit · GitHub
So apparently a custom component is the way to go.

1 Like

Hi,
this is exactly the same problem that I have! Did you solve it in the meantime?
Maybe we can work together. I tried using the microsoft authentication library (msal) for python.
It works to some extent…

Azure populates some environment variables that I retrieved, I defined the others:

APPLICATION_PERMISSIONS = ["https://database.windows.net//.default"]
CLIENT_ID = os.environ["WEBSITE_AUTH_CLIENT_ID"]
AUTHORITY = "https://login.microsoftonline.com/<some code here (client or App ID?)>"
CLIENT_SECRET = os.environ["MICROSOFT_PROVIDER_AUTHENTICATION_SECRET"]

I then instantiate the client app:

client_app = msal.ConfidentialClientApplication(
    CLIENT_ID, authority=AUTHORITY,
    client_credential=CLIENT_SECRET
)

I manage to receive a bearer or access token, telling me that someone authenticated, but not who.

result = client_app.acquire_token_for_client(scopes=APPLICATION_PERMISSIONS)
token = result['access_token']
token_decoded = base64.b64decode(token)

I am stuck requesting an id token to actually identify the authenticated user. I guess that you do it like this (I defined the tasks.read scope in the azure portal):

auth_code_flow = client_app.initiate_auth_code_flow(scopes=["tasks.read"])
id_token = client_app.acquire_token_by_auth_code_flow(auth_code_flow, auth_response={})

However, I have no idea what the auth_response dict should contain. The msal documentation is not at all explicit about this. It just says:

auth_response (dict) – A dict of the query string received from auth server.

I found this on stackoverflow, but the answers are as cryptic to me…

Yeah I did find a solution to read the user email from the http headers:

import streamlit as st
from streamlit.script_run_context import get_script_run_ctx
from streamlit.server.server import Server

def read_aad_username():
    session_id = get_script_run_ctx().session_id
    session_info = Server.get_current()._get_session_info(session_id)
    headers = session_info.ws.request.headers

    headers = headers._dict

    if "X-Ms-Client-Principal-Name" in headers:
        username = headers["X-Ms-Client-Principal-Name"]
        st.write(f"Logged in as {username}")
    else:
        st.warning(f"could not directly read username from azure active directory.")
        username = None
    return username

If you need more info about the user then you have to send a query to the “/.auth/me” endpoint with the auth-token in the http header. You will also have to give your apps’ service principal the necessary rights to read that data about the user.
However, if you are running a Linux WebApp in azure, be aware of a current bug in active directory which makes it necessary for users to delete browser cookies after a restart: Restarting Azure App Service on Linux with Azure Active Directory authentication resets /.auth/me/

import streamlit as st
from streamlit_javascript import st_javascript

def read_aad_username():
    js_code = """(await fetch("/.auth/me")
    .then(function(response) {return response.json();}).then(function(body) {return body;}))
    """

    return_value = st_javascript(js_code)

    username = None
    if return_value == 0:
        pass # this is the result before the actual value is returned 
    elif isinstance(return_value, list) and len(return_value) > 0:
        username = return_value[0]["user_id"]
        st.write(f"Logged in as {username}")
    else:
        st.warning(f"could not directly read username from azure active directory: {return_value}.")
        st.warning(f"A workaround to this is to clear your browser cookies for this site and reloading it.")
    return username

But that is only needed if you need to know more than just the user email address in which case the first version is enough.

1 Like

This works!
Great, muchas gracias!

At least the first version works in reading only the username.
The other one always fails, even if I delete all cookies and user incognito browsing mode.
But I am good for now with only the username!

for the second one to work your web-app will need to be configured with the correct permissions, otherwise you will always get an empty list from the /.auth/me endpoint.