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. In this guide, we'll be using http://localhost:8000
.
You can choose whichever URL you want for the Application URL, 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.
propelauth.py
import streamlit as st
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()
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():
try:
return st.context.cookies["__pa_at"]
except:
return None
def get_refresh_token():
try:
return st.context.cookies["__pa_rt"]
except:
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.
Adding the Authentication Proxy
We'll be using a proxy to protect your Streamlit app. Get started by creating a new repository that is separate from your Streamlit app. We'll eventually be deploying both the proxy and Streamlit app as two separate deployments.
In the new repository, 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:
proxy.mjs
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
andYOUR_API_KEY
with the same values we used in thepropelauth.py
file from earlier. -
The
proxyPort
andurlWhereYourProxyIsRunning
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. Port8501
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:
Logging Users Out
As you may have noticed, the propelauth.py
script above includes a logout function. This function will invalidate the user's refresh token which in turn prevents the user from generating new access tokens.
While this is a critical step in logging a user out, there is still another step required to ensure the user is no longer logged in. In your Streamlit app, you can include a logout button like so:
with st.sidebar:
if st.button("Logout"):
auth.logout()
st.markdown(
"""
<meta http-equiv="refresh" content="0; URL='/api/auth/logout'" />
""",
unsafe_allow_html=True,
)
This will run the logout function and also redirect the user to /api/auth/logout
, which is handled by the @propelauth/auth-proxy
library. The auth-proxy will clear the necessary cookies from the browser before redirecting the user back to your login page.
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.
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.
Deploying the Proxy
Once a URL is generated, we can update the proxy.mjs
file with the new target host. While we're here, we can also update the proxy domain. Remember, users will be accessing your app via the proxy, so the URL for your proxy will be the one your users will be navigating to. It should also match the Application URL that you used in the Frontend Integration page in PropelAuth.
proxy.mjs
import { initializeAuthProxy } from '@propelauth/auth-proxy'
// Replace with your configuration
await initializeAuthProxy({
authUrl: "YOUR_AUTH_URL",
integrationApiKey: "YOUR_API_KEY",
proxyPort: 8000,
urlWhereYourProxyIsRunning: "YOUR_PROXY_URL", // https://proxy-example.com
target: {
host: "YOUR_APP_URL", // streamlit-example.com
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
.
As mentioned earlier, users will access your app via the proxy so it's crucial that the URL of your proxy is whats set for your production environment in the Frontend Integration page.
Once deployed, navigate to your proxy's URL and login. You're now live!