Streamlit Authentication
Tags: python, programming, tutorial, datascience
Published: 2024-01-19 21:23:34

[**Streamlit**](https://streamlit.io/?ref=propelauth.com) is a powerful tool for creating “data apps.” The easiest way to understand it is with this picture (from Streamlit’s marketing site):


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dutihy2povo28os2w5h6.png)

You write python code ⇒ You get an interactive web application.

It integrates easily with libraries you already know, like pandas and numpy. And the interactivity is way too easy, like this application where the `age` variable updates as the user changes the slider:

```python
import streamlit as st

age = st.slider('How old are you?', 0, 130, 25)
st.write("I'm ", age, 'years old')
```

There’s no [**useEffect**](https://react.dev/reference/react/useEffect?ref=propelauth.com) or worrying about re-renders or anything like that.

## **One small quirk… Authentication is difficult**

Streamlit does have a few quirks, however, and one of them is that the out-of-the-box [**authentication options**](https://docs.streamlit.io/knowledge-base/deploy/authentication-without-sso?ref=propelauth.com) are limited.

We wanted an approach to authentication that was as simple as Streamlit is, but still powerful enough to include features like 2FA (including enrolling) and organizations (for your B2B apps). Ultimately, on the Streamlit side, it looks like this:

```python
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)
```

If the user isn’t logged in, nothing else runs. If they are logged in, you’ll get an object with their information.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8oo41olup99rj025da3.png)

So… how does this work? And what is the account page button on the sidebar?

To make this work, we rely on two PropelAuth features:

* **PropelAuth’s hosted pages.** These are a set of UIs that we host for you on your domain. This includes everything you need for authentication (signup, login, forgot password, etc) but also extra UIs like an account page so your users can enroll in 2FA or update their information.
    
* **PropelAuth’s authentication proxy**. This is a lightweight service which sits on top of Streamlit which will handle auth. If the user isn’t logged in, it’ll send them over to the login page. If they are logged in, it’ll pass their information securely to Streamlit.
    

## **Setting it up**

The only thing you’ll need is a [**PropelAuth**](https://app.propelauth.com/?ref=propelauth.com) account. We’ll first look at how to set this up locally.

## **PropelAuth Test Environment Setup**

The only unique thing to Streamlit is you’ll need the following configuration on the Frontend Integration page of the dashboard:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9b09pe1dw7apgakgby0h.png)

You can choose whichever port, 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.

You can then modify any of the other settings, like which login methods you want to support or if you allow signups or if you will be manually onboarding users.

## **Protecting Streamlit - Initialization**

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

```bash
pip install propelauth_py
```

Then create a new file called [`propelauth.py`](https://streamlit.io/?ref=propelauth.com) and copy the following code in. This is a helper class which checks to see if the user is logged in or not.

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

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

    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 get_access_token():
    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() == "__pa_at":
            return split_cookie[1].strip()

    return None
```

At the bottom or in a separate file, add the following:

```bash
# Configuration, please edit
auth = Auth(
    "YOUR_AUTH_URL",
    "YOUR_API_KEY"
)
```

You can find these in the **Backend Integration** settings of your PropelAuth dashboard. You likely want to load them as environment variables instead of hardcoding them.

## **Protecting Streamlit - Usage**

In your application, you can now use that snippet of code we had above:

```python
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)
```

If you go to test your app, you should see that you are never logged in and will get an Unauthorized error. We’ll fix that now.

## **Adding the Authentication Proxy**

Next, we’re going to set up the proxy. Create a new directory called `proxy` and then install the proxy:

```bash
npm i @propelauth/auth-proxy
```

Then, create a file called `proxy.mjs` and add the following contents:

```jsx
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>',
    targetHost: 'localhost',
    targetPort: 8501,
})
```

The authUrl and API key are the same ones we used above (from the **Backend Integration** page of the dashboard). In this example, we were running the proxy locally on port 8000 (as you can see from the config), and we were running Streamlit locally on port 8501.

Once you have your [**streamlit app running**](https://docs.streamlit.io/knowledge-base/using-streamlit/how-do-i-run-my-streamlit-script?ref=propelauth.com), all you have to do is run it:

```bash
node proxy.mjs
```

## **Testing**

If we go directly to [**http://localhost:8051**](https://streamlit.io/?ref=propelauth.com), we will still see an unauthorized error, as we should always go through the proxy.

When we go to [**http://localhost:8000**](https://streamlit.io/?ref=propelauth.com), however, we’ll be redirected to a login page. After logging in, we’ll be redirected back to our Streamlit app, and we’ll see our user’s information.

If we click the account button in the sidebar, we are redirected to a dedicated page for managing our account:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vo567quchjnesoloym2l.png)

## **Deploying to Production with Docker**

To go to production, you’ll just need a way to host the authentication proxy. If you are already deploying [**Streamlit with Docker**](https://docs.streamlit.io/knowledge-base/tutorials/deploy/docker?ref=propelauth.com), you can deploy the authentication proxy with Docker as well with this Dockerfile:

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

Your users will then go to the URL where your authentication proxy is hosted, and not the URL where Streamlit is hosted.

Depending on your infrastructure setup, you can actually keep Streamlit entirely private and only accessible from the proxy, but this isn’t a requirement. Users that go directly to Streamlit will always be met with an “Unauthorized” page.

And now you should be good to go! Just make sure you don’t forget to update your configuration with your [**production values from PropelAuth**](https://docs.propelauth.com/getting-started/deploy-to-prod?ref=propelauth.com) as well.