[Feedback requested!] Proposal - Native authentication support for Streamlit

We are investigating approaches to add native authentication support for Streamlit, as it is one of the top most requested community features. This is a rough proposal for how it might work, which is shared for your feedback.

This is cross-posted in Github issue #8518

Goals

Goal 1: Provide the building blocks for integrating more robust auth solutions including:

  • documented support for reading headers (replace the undocumented but heavily used _get_websocket_headers()
  • native support for reading and writing cookies ( #861 )
  • native redirect ( #7123 )
  • hooks to add identity information in st.user
  • [longer term] Support custom http endpoints ( #439 ) - this will be a bigger effort but know we probably need to get there eventually to have a really robust support
  • ??? What else ???

Goal 2: Provide an out-of-the-box native integration which covers the most common use cases, especially for developers getting started. To that end, we propose standardizing on OAuth2 & OpenID Connect as the natively supported protocols in Streamlit.

  • This would be an opinionated and minimal implementation, geared towards integrating with standardized OIDC providers.
  • Goal would be, with a few lines of config / secrets, you can be set up with login() / logout() methods and the users verified email, OAuth2 token, and potentially other claims from a JWT readily available through a standard API (likely attributes on st.user).
  • Ideally, include an easy integration with cookies support so it’s trivial to maintain the user auth session across tabs for some time. However, we still need to investigate whether this is feasible or has other major implications.

Why OpenID Connect (OIDC)?

  • From our perspective, OIDC seems like the most modern, standardized authentication protocol we can use which enables a standard format of user identity claims (including roles / groups) and minimal custom code across providers.
  • We also see a wide range of adoption across identity providers both in the consumer space (Google, Apple, etc) and enterprise space (Auth0, AWS Cognito, MSFT Entra ID, etc).
  • For most app developers who aren’t working in a more sophisticated existing platform, it seems to cover the typical use case for “single sign on” for relatively low fuss and configuration required in the provider side.

Non-goal: Replace the use of reverse proxy in higher security environments

We know this practice of running Streamlit behind an auth proxy and forwarding authenticated requests to the app is fairly common among Streamlit usage in bigger companies or those with higher security requirements. We want to build library features that make that easier to run (such as documented headers support) but not replace it or build extremely hardened security into Streamlit, since it isn’t our expertise.

But, we think this proposal can still unblock many developers operating in somewhat lower stakes environments who experience a lot of friction today.

VERY ROUGH API sketch

Here’s a very rough first idea of how the API might work for the built-in OIDC.

# .streamlit/secrets.toml or similar

[auth] # reserved group for defining auth, could also support multiple like [auth.google]
type = "oidc"
provider = "google" # or api_base_url="accounts.google.com"
client_id = "SOME ID"
client_secret = "SOME SECRET"
default_ttl = "7d" # if we have some easy cookie support

Once this is configured, you can call some standard methods in the app which will redirect to the appropriate OIDC flow. Streamlit will automatically expose an OAuth2 callback endpoint which handles the response from the identity provider.

In the case below, calling login() will simply redirect to the OAuth2 flow. When the user returns from the flow, their identity values will be available in st.user. Calling logout() will clear the values from st.user in that session along with any browser cookies.

# streamlit_app.py

if st.user.logged_in(): # verify log in, including cookie from an earlier session
    st.write(f"Welcome, {st.user.name}!")
    st.sidebar.button("Logout", on_click=st.user.logout)
else:
    st.sidebar.button("Login", on_click=st.user.login)

Other authentication protocols

We know other protocols are used today, such as looking up a username / password in a database (e.g. Streamlit-Authenticator), or more complex delegated authentication schemes like SAML / LDAP. In some cases, the existing plugins / components for these can work pretty well. We’d like to provide some hooks to make them more consistent with the native support (maybe similar to st.connection), but it may not be in the initial launch.

What do you think?

If this proposal and approach sounds good and meets your use case, please give a :+1: . Also, we’d love to hear any comments about what you like, don’t like, or would change, or any use case you have which would not be supported. Thanks!!

17 Likes

Hi @jcarroll ,

I am excited by your proposal for native authentication integration within the Streamlit framework. This is a feature that would certainly enhance the user experience and streamline the development process.

Currently, I am using the “Authentication boilerplate with Firebase Web API” solution proposed by @marduk in July 2023. It has been working well for me, but I am curious to know if there are plans to integrate Firebase as an authentication provider natively within Streamlit.

Firebase has proven to be a reliable and efficient solution for authentication, and having it integrated natively would be a significant improvement.

Looking forward to hearing your thoughts on this.

2 Likes

Very good proposal @jcarroll. I thank Streamlit for hearing the community and delving deeper into this issue.

I am not an expert in authentication, but 2 things I’m seeing for JavaScript authentication frameworks that I would very much like to see in Streamlit:

  1. Middleware. It is capable of redirecting the user to a login page if he tried to access a page that unauthenticated users aren’t supposed to access. There are some more advanced use cases where middleware can be used to see the user location to give him a custom experience if he is from a certain location. That would be a big plus if possible to implement natively on Streamlit.

  2. Authenticated avatars. Big login providers such as Google, GitHub and Microsoft usually offer a image/photo of the user that can be accessed on JS frameworks to build an avatar of the user for, say, a navigation bar. That usually comes with a fallback image for when the user doesn’t have an image. That implemented on Streamlit would bring the developer and user experience to something much closer to a JS full-stack web app.

1 Like

That would be fantastic! I am also relying on this workaround.

3 Likes

My vote is to include LDAP. That still seems to be the default / base for many orgs. SSO via Oath2 may not be available, or communication unreachable from where the Streamlit app is deployed.

There seems to be a current third-party component for LDAP, but I’m probably not alone in having hesitation for relying on a third party for a service like auth. Would love to see that as first-party / native.

1 Like

@jcarroll looks good. I really like better access to headers, cookies and tokens. And I do understand you target to cover only some baseline.

A few questions from me:

  • Are you considering only authentication, i.e. with no authorization? It would be nice to be able to decorate function calls (already going this way with dialog and fragment; I was going this way long ago with menu items and their respective rendering functions - that required quite some work) to limit visibility / accessibility, say, by user role present in SSO JWT.
  • Did you consider local auth? For example, if auth server is not available, or for some kind of initial setup, or if SSO is not meant to be used at all, but there’s a need to limit access. I’d definitely like a way to use locally defined accounts. Something like ability to add a separate auth provider in config, but targeting local file (json, toml, yaml, etc) with accounts.
  • Do you think support of multiple identity providers is possible? Say, both google and apple. (saw a mention in code snippet later, would be nice to clarify this point)
  • For cookies - I see no mention on cloud shared environments, including Streamlit cloud. Just wanted to highlight that cookies overlap there. (see streamlit_cookies_manager, outdated, a bit lacking, but with 2 nice ideas - encryption and prefixes)

Updated: regarding multiple login sources - what I meant is a login form with support for login/password (if there are means that accept them directly - local or LDAP) and buttons for SSOs reroutes.

1 Like

Thank you @CHerSun !

For authorization, I don’t think we’ll provide any built-in decorator support soon although I could imagine something like that further in the future. The preferred approach is to make any role / group information such as from a JWT easily available in the st.user object for the app developer to work with as needed.

For questions about local and multiple auth - yes we want to provide a way to configure those. I don’t know if we will provide a new “local” auth solution since many of the existing plug-ins / components already provide that, but we should make a native auth solution play well with those.

For cookies - yes, the shared environments are a concern and we need to make sure we hae a good technical solution to support that. We won’t ship something that makes it easy to accidentally use a cookie that’s easy to steal from another app.

For authorization, I don’t think we’ll provide any built-in decorator support soon although I could imagine something like that further in the future. The preferred approach is to make any role / group information such as from a JWT easily available in the st.user object for the app developer to work with as needed.

Looks good. Just in case my previous work was:

  • menu item - name, icon, … + rendering function
  • rendering function - decorated with auth func
  • auth func - gets UserInfo, returns bool if user is authorized (via lambda). Lambda is stored as hidden field right on decorated function. Auth func silently skips decorated function, if False (with ability to raise PermissionError via a flag instead). Hidden lambda is used by menu - to decide if to show menu item on render (on True).

a new “local” auth solution since many of the existing plug-ins / components already provide that

yep, but it might be difficult to use embedded auth + some local plugin. Unless you provide some junction.

We won’t ship something that makes it easy to accidentally use a cookie that’s easy to steal from another app.

:+1:

@jcarroll first, thanks for continuing to engage with us on here and Github :raised_hands:

I would love to read a little progress update.

1 Like

Thank you @jcarroll and everyone else who contributed to the st.context.headers in the 1.37.0 release!

1 Like