Streamlit Authentication

Streamlit is an open-source Python library that allows you to create and share beautiful, interactive data applications and dashboards with minimal code. Streamlit is particularly useful for creating data science and machine learning applications, dashboards, and data visualization tools.

That being said, out-of-the-box authentication options with Streamlit are limited. That's where PropelAuth comes in. This guide will cover how to build and deploy a Streamlit app using PropelAuth for authentication. We'll be using PropelAuth's OAuth2 support alongside Streamlit's OIDC integration.

Setup in PropelAuth

We first need to create an OAuth2 provider within PropelAuth. To do so, navigate to the Frontend Integration page in your PropelAuth dashboard, click on Advanced Settings then Edit OAuth Config.

Navigating to OAuth2 Dashboard Page

Create a New OAuth Client and copy down your Client ID and Client Secret. We'll be needing these for later.

While we're here, let's also add a Redirect URI. Streamlit requires that this value is equal to your app's absolute URL with the pathname oauth2callback. In this guide we'll be using http://localhost:8501/oauth2callback.

OAuth2 Dashboard Page

The last variable we need is our Auth URL. You can find this back in the Frontend Integration page.

Installation and Initialization

Let's begin by navigating to (or creating if you haven't done so already) your Streamit app's secrets.toml file. Here, we'll use the Auth URL, Redirect URI, Client ID, and Client Secret that we retrieved in the previous step.

You'll also need to create a cookie_secret which is a randomly generated string. Here's Streamlit's documentation on it.

.streamlit/secrets.toml

[auth]
redirect_uri = "{YOUR_REDIRECT_URI}" # "http://localhost:8501/oauth2callback"
cookie_secret = "{RANDOMLY_GENERATED_STRING}"
client_id = "{YOUR_CLIENT_ID}"
client_secret = "{YOUR_CLIENT_SECRET}"
server_metadata_url = "{YOUR_AUTH_URL}/.well-known/openid-configuration"
client_kwargs = { "prompt" = "login" } # Optional.
# Will force the user to go through the login flow.

In your Streamlit project, install the propelauth-py library:

pip install propelauth_py

Now create a new file in your root directory and name it propelauth.py. Copy the following code into it, making sure to edit YOUR_AUTH_URL and YOUR_API_KEY with the values found in your Backend Integration page in PropelAuth. You likely want to load them as environment variables instead of hardcoding them.

propelauth.py

import streamlit as st
from propelauth_py import init_base_auth

# Replace me
AUTH_URL = "YOUR_AUTH_URL"
API_KEY = "YOUR_API_KEY"

class Auth:
    def __init__(self, auth_url, integration_api_key):
        self.auth = init_base_auth(auth_url, integration_api_key)
        self.auth_url = auth_url
        self.integration_api_key = integration_api_key
        self.access_token = None

    def get_user(self, user_id):
        try:
            if self.access_token is None:
                return self.force_refresh_user(user_id)
            return self.auth.validate_access_token_and_get_user(f"Bearer {self.access_token}")
        except UnauthorizedException:
            return self.force_refresh_user(user_id)
            
    def force_refresh_user(self, user_id):
        access_token_response = self.auth.create_access_token(user_id, 10)
        self.access_token = access_token_response.access_token
        return self.auth.validate_access_token_and_get_user(f"Bearer {self.access_token}")
    
    def get_account_url(self):
        return self.auth_url + "/account"
    
    def log_out(self, user_id):
        self.auth.logout_all_user_sessions(user_id)
        self.access_token = None
        st.logout()

auth = Auth(
    AUTH_URL,
    API_KEY
)

This is a helper class which will help retrieve the user's information, redirect to the user's hosted account page, and log the user out. We'll cover some of these functions in this guide, but feel free to use these functions or add more yourself!

And we're all setup! Let's move on to protecting your Streamlit pages as well as displaying the logged in user's information.

Protecting Pages And Displaying User information

Before we move on, it's important to understand the difference between Streamlit's st.experimental_user and the User object we get back from the function get_user found in our propelauth.py script.

The st.experimental_user is the user object that is returned from Streamlit after the user authenticates successfully. It's missing critical information about the user, such as which organizations they are members of as well as any custom user properties they may have. On the other hand, the user returned from get_user will include all of this information as well as helper functions such as get_orgs and is_role_in_org.

That being said, st.experimental_user is very good at checking if the user is logged in or not. In this example, we'll use st.experimental_user to check if the user is authenticated. If not, we'll redirect the user to login via PropelAuth using Streamlit's st.login command.

import streamlit as st
from propelauth import auth

if st.experimental_user.is_logged_in:
    user = auth.get_user(st.experimental_user.sub)
    if user:
        st.write(user)
        
else:
    st.write("You are not logged in.")
    st.button("Login", on_click=st.login)
    st.stop()

Above, we first check if the user is authenticated. If the user is not logged in, we'll display a Login button like so:

User not authenticated

If the user is logged in, we'll fetch the full User object using get_user and then display the returned information.

User authenticated

Authorization / organizations

You can also verify which organizations the user is in, and which roles and permissions they have in each organization using the User object.

Check Org Membership

Verify that the user is logged in and that the user is a member of the specified organization.

def return_org_name(org):
    return org.org_name

if st.experimental_user.is_logged_in:
    user = auth.get_user(st.experimental_user.sub)
    org = st.selectbox("Select an organization", user.get_orgs(), format_func=return_org_name)
    if org:
        st.write(f"You are in org {org.org_id}")
    else:
        st.write("You do not belong to this org")

Check Org Membership and Role

Similar to checking org membership, but will also verify that the user has a specific Role in the organization. This can be done using either the User or OrgMemberInfo objects.

A user has a Role within an organization. By default, the available roles are Owner, Admin, or Member, but these can be configured. These roles are also hierarchical, so Owner > Admin > Member.

def return_org_name(org):
    return org.org_name

if st.experimental_user.is_logged_in:
    user = auth.get_user(st.experimental_user.sub)
    org = st.selectbox("Select an organization", user.get_orgs(), format_func=return_org_name)
    role_to_check = "Admin"
    if org and org.user_is_role(role_to_check):
        st.write(f"You are an Admin in org {org.org_id}")
    else:
        st.write("You must be an Admin to access this page")

Check Org Membership and Permission

Similar to checking org membership, but will also verify that the user has the specified permission in the organization. This can be done using either the User or OrgMemberInfo objects.

Permissions are arbitrary strings associated with a role. For example, can_view_billing, ProductA::CanCreate, and ReadOnly are all valid permissions. You can create these permissions in the PropelAuth dashboard.

def return_org_name(org):
    return org.org_name

if st.experimental_user.is_logged_in:
    user = auth.get_user(st.experimental_user.sub)
    org = st.selectbox("Select an organization", user.get_orgs(), format_func=return_org_name)
    permission_to_check = "can_view_billing"
    if org and org.user_has_permission(permission_to_check):
        st.write(f"You can view billing information for org {org.org_id}")
    else:
        st.write("You do not have the necessary permission to view billing information.")

Calling Backend APIs

As you may have noticed, this guide uses PropelAuth's propelauth-py library to retrieve the user's information, among other things. This means you can use the propelauth-py library to make requests to PropelAuth's Backend API, allowing you to fetch members of an organization, create orgs, and a lot more.

from propelauth import auth

magic_link = auth.auth.create_magic_link(email="test@example.com")

Logging Out

To log a user out of your Streamlit app you can use the log_out function found in your propelauth.py file.

import streamlit as st
from propelauth import auth

if st.experimental_user.is_logged_in:
    if st.button("Logout"):
        auth.log_out(st.experimental_user.sub)

Logging Out Through The Hosted Pages

If you're using PropelAuth's hosted pages you may find that the Log Out button found in the Account Page Sidebar does not completely log the user out of their Streamlit session.

Hosted pages logout button

Removing the Log Out Button

There are a couple of ways to address this. First, you can remove the Sidebar by navigating to the Look & Feel section of your PropelAuth Dashboard, clicking Open Editor followed by Management Pages towards the top.

Find the Display Sidebar setting and disable it.

Disable hosted pages sidebar

The Sidebar as well as the Logout button are now removed from the hosted pages.

Creating a Logout Page

If you would like to keep the Log Out button, we recommend creating a page in your Streamlit app that will automatically log users out when redirected to. Let's create a page that is located at /logout:

import streamlit as st
from propelauth import auth

if st.experimental_user.is_logged_in:
    auth.log_out(st.experimental_user.sub)
        
st.switch_page("home.py") # redirect back to your home page

If a logged in user were to visit this page they'll be logged out and then redirected back to your home page. The second step here is to set your Default redirect path after logout setting, found in your Frontend Integrations page in the PropelAuth Dashboard. This is the path where users will be redirected to after clicking the Log Out button found in the hosted pages.

You can set this to /logout, or wherever you decided to place your logout page. Now when the user clicks the Log Out button, they'll first be logged out of the hosted pages, redirected to the /logout page of your application where they'll be logged out of Streamlit, and then redirected back to your home page.

Setting default redirect after logout

Have any questions? Please reach out to support@propelauth.com.