Back to Streamlit

Streamlit user authentication

lib/streamlit/.agents/skills/developing-with-streamlit/references/authentication.md

1.62.1.dev202608304.8 KB
Original Source

Streamlit user authentication

Add real user sign-in with st.login, st.logout, and st.user.

Don't roll your own password gate

A st.text_input(type="password") compared against a hardcoded string (or a secret) is not authentication. It has no concept of user identity, the "password" is shared by everyone, and the gate is trivially bypassed. Use st.login() with a real OpenID Connect (OIDC) identity provider instead.

python
# BAD — a fake gate, not authentication
import streamlit as st

if st.text_input("Password", type="password") != "hunter2":
    st.stop()
st.write("Secret dashboard")
python
# GOOD — real OIDC identity via st.login
import streamlit as st

if not st.user.is_logged_in:
    st.login()
    st.stop()

st.write(f"Hello, {st.user.name}!")
if st.button("Log out"):
    st.logout()

Why st.login:

  • Real per-user identity from a trusted provider (Google, Microsoft, Okta, Auth0, …)
  • Signed identity cookie — can't be faked client-side
  • Configuring [auth] turns on XSRF protection automatically
  • st.user exposes verified claims (email, name, etc.); no password handling in your code

Requirements

The auth flow needs the optional auth extra, which pulls in Authlib:

shell
pip install "streamlit[auth]"

This is app-level authentication. It is unrelated to st.text_input(type="password"), which is just a masked text field with no identity behind it.

Configure the provider in secrets.toml

All auth config lives in an [auth] section of .streamlit/secrets.toml. Three keys are shared across every provider: redirect_uri, cookie_secret, and the per-provider client_id, client_secret, and server_metadata_url.

toml
# .streamlit/secrets.toml
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "a-strong-randomly-generated-secret"
client_id = "xxx"
client_secret = "xxx"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
  • redirect_uri must be your app's absolute URL ending in /oauth2callback. For local dev on the default port that's http://localhost:8501/oauth2callback. Update it (in both secrets and your provider) when you deploy.
  • cookie_secret signs the identity cookie. Use a long, random value.
  • client_id, client_secret, and server_metadata_url come from your OIDC provider's app registration.

Never commit this file. Add it to .gitignore:

.streamlit/secrets.toml

Gate the whole app

The canonical pattern: bail out early when the user isn't signed in, then render protected content.

python
import streamlit as st

if not st.user.is_logged_in:
    st.title("Please log in")
    if st.button("Log in with Google"):
        st.login()
    st.stop()

# Everything below only runs for authenticated users
st.title("Dashboard")
st.write(f"Signed in as {st.user.name} ({st.user.email})")

with st.sidebar:
    if st.button("Log out"):
        st.logout()

st.user.is_logged_in is True only after a successful st.login(). st.user.email, st.user.name, and other fields are claims parsed from the provider's identity token (available claims vary by provider). st.logout() clears the identity cookie and starts a fresh session.

Multiple named providers

Give users a choice of identity providers by adding [auth.<provider>] sections. Keep redirect_uri and cookie_secret in the shared [auth] section, and put each provider's credentials in its own section. The name is internal to your app and is passed to st.login("<provider>").

toml
# .streamlit/secrets.toml
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "a-strong-randomly-generated-secret"

[auth.google]
client_id = "xxx"
client_secret = "xxx"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"

[auth.microsoft]
client_id = "xxx"
client_secret = "xxx"
server_metadata_url = "https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration"
python
import streamlit as st

if not st.user.is_logged_in:
    st.header("Log in:")
    if st.button("Google"):
        st.login("google")
    if st.button("Microsoft"):
        st.login("microsoft")
    st.stop()

st.write(f"Hello, {st.user.name}!")
if st.button("Log out"):
    st.logout()

Provider names can't contain underscores. Only OIDC providers are supported — generic OAuth 2.0 providers won't work.

References