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.

Setup in PropelAuth

Lets start by navigating to your Frontend Integrations page in your PropelAuth dashboard. We'll be using a proxy to protect your Streamlit app (more on that later), so make sure to set a port that's different from where your Streamlit app is located.

Frontend integrations page

You can choose whichever port you want, but you must use /api/auth/callback for login and /api/auth/logout for logout. You will still be redirected back to your main page, but those will handle logging the user in/out.

Installation and Initialization

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.

from propelauth_py import init_base_auth, UnauthorizedException
from streamlit.web.server.websocket_headers import _get_websocket_headers
import requests

# 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

    def get_user(self):
        access_token = get_access_token()
        print(access_token)

        if not access_token:
            return None

        try:
            return self.auth.validate_access_token_and_get_user("Bearer " + access_token)
        except UnauthorizedException as err:
            print("Error validating access token", err)
            return None

    def get_account_url(self):
        return self.auth_url + "/account"
    
    def logout(self):
        refresh_token = get_refresh_token()
        if not refresh_token:
            return False

        logout_body = {"refresh_token": refresh_token}
        url = f"{self.auth_url}/api/backend/v1/logout"
        headers = {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + self.integration_api_key,
        }
        
        response = requests.post(url, json=logout_body, headers=headers)
        
        return response.ok


def get_access_token():
    return get_cookie("__pa_at")

def get_refresh_token():
    return get_cookie("__pa_rt")

def get_cookie(cookie_name):
    headers = _get_websocket_headers()
    if headers is None:
        return None

    cookies = headers.get("Cookie") or headers.get("cookie") or ""
    for cookie in cookies.split(";"):
        split_cookie = cookie.split("=")
        if len(split_cookie) == 2 and split_cookie[0].strip() == cookie_name:
            return split_cookie[1].strip()

    return None

auth = Auth(
    AUTH_URL,
    API_KEY
)

This is a helper class which checks to see if the user is logged in or not.

Lets now attempt to load PropelAuth user data from your Streamlit application. Import the helper class from above and use the get_user() function to retrieve user data. If the user is not logged in, display an error message that the user is unauthorized.

import streamlit as st

from propelauth import auth

user = auth.get_user()
if user is None:
    st.error('Unauthorized')
    st.stop()

with st.sidebar:
    st.link_button('Account', auth.get_account_url(), use_container_width=True)

st.text("Logged in as " + user.email + " with user ID " + user.user_id)

You should be seeing the Unauthorized error! This is because the user is not logged in. To fix this, let's now set up the auth proxy mentioned earlier.

Unauthorized error

Adding the Authentication Proxy

From your root directory, create a new directory called proxy, navigate to it, and install the proxy from PropelAuth:

npm i @propelauth/auth-proxy

Within the proxy directory create a new file called proxy.mjs and add the following:

import { initializeAuthProxy } from '@propelauth/auth-proxy'

// Replace with your configuration
await initializeAuthProxy({
    authUrl: "YOUR_AUTH_URL",
    integrationApiKey: "YOUR_API_KEY",
    proxyPort: 8000,
    urlWhereYourProxyIsRunning: 'http://localhost:8000',
    target: {
        host: 'localhost',
        port: 8501,
        protocol: 'http:'
    },
})
  • Make sure to replace YOUR_AUTH_URL and YOUR_API_KEY with the same values we used in the propelauth.py file from earlier.

  • The proxyPort and urlWhereYourProxyIsRunning should match the Application URL that you set in your Frontend Integrations page in PropelAuth.

  • The target parameters are where your Streamlit app is located. Port 8501 is the default port so you'll likely not have to change this.

Running Your Application Locally

You should be ready to test your app locally! First, run your Streamlit app:

streamlit run your_script.py

Once that's running, it's time to start the proxy server:

node proxy.mjs

If we navigate straight to your Streamlit app (http://localhost:8501 by default), we'll still run into the Unauthorized error. However, if we navigate to the proxy server instead (http://localhost:8000), you'll be redirected to login to PropelAuth and then back to the proxy which will now be displaying your user information:

Displaying user information

Deploying Your Streamlit Application

This part of the guide will cover how to deploy your Streamlit app to Google Cloud Run using Docker.

Lets start by navigating to the Go Live page in the PropelAuth dashboard and verifying your domain.

Once verified, we need to set our Frontend Integration locations. We still need to use /api/auth/callback for login and /api/auth/logout for logout. You can also navigate to the Backend Integrations page to retrieve your YOUR_AUTH_URL and YOUR_API_KEY.

From now on in the guide, anytime you see YOUR_AUTH_URL and YOUR_API_KEY, populate these fields with your production environment variables.

Starting with your Streamlit app, let's adjust your Dockerfile so CORS is disabled by adding the --server.enableCORS=false command. Here's an example:

FROM python:3.8
ENV auth_url=YOUR_AUTH_URL
ENV auth_api_key=YOUR_API_KEY
EXPOSE 8501
ADD requirements.txt requirements.txt
RUN pip install -r requirements.txt
WORKDIR /app
COPY . ./
ENTRYPOINT ["streamlit", "run", "app.py",  "--server.address=0.0.0.0", "--server.enableCORS=false"]

When we create a service in Cloud Run from this container, make sure to edit the Container Port field in Cloud Run to 8501, or whichever port you exposed in your Docker container.

Cloud Run Port

Since users will access your app via the proxy, the URL for the Streamlit part of your app is not important and can simply be the URL that Cloud Run generates for you.

Once a URL is generated, lets update the proxy.mjs file so it targets the deployed Streamlit app. We can also add the domain for your app to it before deploying.

import { initializeAuthProxy } from '@propelauth/auth-proxy'

// Replace with your configuration
await initializeAuthProxy({
    authUrl: "YOUR_AUTH_URL",
    integrationApiKey: "YOUR_API_KEY",
    proxyPort: 8000,
    urlWhereYourProxyIsRunning: "YOUR_APP_URL",
    target: {
        host: "YOUR_PROXY_URL,
        protocol: 'https:'
    },
})

Lets now build a Docker container using the following Dockerfile:

FROM node:20-slim
ENV auth_url=YOUR_AUTH_URL
ENV auth_api_key=YOUR_API_KEY
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
USER root
COPY package*.json ./
RUN npm install
COPY --chown=node:node . .
EXPOSE 8000
CMD [ "node", "proxy.mjs" ]

When we create a service in Cloud Run for the proxy container, make sure to edit the Container Port field in Cloud Run to 8000.

Google Cloud Proxy Port

As mentioned earlier, users will access your app via the proxy so it's crucial that the URL of your proxy is what's set for your production environment in the Frontend Integration page.

Once deployed, navigate to your proxy's URL and login. You're now live!