Hi everyone,
The past few days I also have been working to figure out my token issues so I feel itâs a good time to add a few bits to the discussion. Unfortunately I cannot authoritatively speak to the security of these approaches so I welcome any comments.
@mstaal Thanks for the component! In my case (and I believe OPâs case), the frontend part of the login is handled by Azure, but I was still following this thread to figure out how to securely read other claims.
My use case
I assume not everyone here has the same aims. I am using the built-in App Service authentication (âEasyAuthâ) configured to provide single-tenant access, for users in my organization only & restricted by group access.
In that case, the App Service provides the email of the logged in user in the HTTP headers, as mentioned by others already. I wanted to access other claims, particularly the application roles for the user to provide some more fine-grained access control. At the same time, frontend development is not my forte so I did not want to stray far from the built-in authentication module or worry about custom JS components.
The vanishing tokens
My most basic problem was the bug mentioned by @thunderbug1 . Because I am running a container from the Python Docker image, every time I updated something I lost both the tokens from the header and the contents of /.auth/me
endpoint! This made it impossible to develop against these services, and for a long time I did not understand why this happened.
The solution is as follows:
- Make a dedicated container in a storage account.
- Generate a SAS token with read/write/list permissions for the contents of the container.
- In the App Service, go to Configuration and add a
WEBSITE_AUTH_TOKEN_CONTAINER_SASURL
Application Setting with the full SAS URL for the container as the value.
- The container now serves as the token store and tokens wonât be deleted anymore.
The token approach
Now that the tokens are fixed, the dict returned by _get_websocket_headers()
has an "X-Ms-Token-Aad-Id-Token"
and "X-Ms-Token-Aad-Access-Token"
. As far as I understand, the Access token can be used to gain access to other resources, while the Id token can be used to get info on the user; in practice they both have similar contents. They might differ if custom claims are configured on the Azure side.
The contents of the ID token could be read without validation using PyJWT:
import jwt
from streamlit.web.server.websocket_headers import _get_websocket_headers
token = _get_websocket_headers()["X-Ms-Token-Aad-Id-Token"]
jwt.decode(jwt=token, options={"verify_signature": False})
The contents can also be validated by Azure signature, but the kicker is that the ID token has a 1-hour expiration window and possibly cannot be refreshed, so I am skipping the code here. I tried setting up refresh tokens and calling the /.auth/refresh
endpoint from streamlit server-side with no luck, for either the ID or access token.
The /.auth/me
approach
As mentioned by @thunderbug1 above, the App Service also provides an /.auth/me
endpoint which can be used to directly read the claims present in the tokens, validated (I believe) by the App Service middleware.
@thunderbug1 's method uses a client-side custom JS component which is probably the more âcorrectâ solution. I made it work in the server Python code using the AppServiceAuthCookie, again from the websocket headers. It works something like this:
import requests
import streamlit as st
host = <my-app-url>
if st.button("`/.auth/me` content"):
cookies = headers["Cookie"].split("; ")
for cookie in cookies:
cookie = cookie.split('=', 1)
if cookie[0]=="AppServiceAuthSession":
auth_cookie = {"AppServiceAuthSession": cookie[1]}
me = requests.get(f"{host}/.auth/me", cookies=auth_cookie)
st.write(me.json())
Then itâs just a matter of picking out values from the JSON response.
The Graph API
Another way is to use the Access token to query the Graph API for user details using the Access token. But since I did not entirely figure out how to refresh the Access token after itâs expired, I have not put too much thought into it.