# 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](https://docs.propelauth.com/overview/authentication/oauth2) alongside Streamlit's [OIDC integration](https://docs.streamlit.io/develop/concepts/connections/authentication).

## [Setup in PropelAuth](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#setup-in-propel-auth)

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**.

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`.

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

## [Installation and Initialization](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#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](https://docs.streamlit.io/develop/api-reference/user/st.login#:~:text=cookie_secret%20should%20be%20a%20strong%2C%20randomly%20generated%20secret.) on it.

### .streamlit/secrets.toml

```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:

```bash
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

```python
import streamlit as st
from propelauth_py import init_base_auth, UnauthorizedException

# 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, user_id):
        try:
            if "access_token" not in st.session_state:
                return self.force_refresh_user(user_id)
            return self.auth.validate_access_token_and_get_user(
                f"Bearer {st.session_state['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)
        st.session_state["access_token"] = access_token_response.access_token
        return self.auth.validate_access_token_and_get_user(
            f"Bearer {st.session_state['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)
        st.session_state["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](https://docs.propelauth.com/overview/basics/hosted-pages#user-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](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#protecting-pages-and-displaying-user-information)

Before we move on, it's important to understand the difference between Streamlit's [st.user](https://docs.streamlit.io/develop/api-reference/user/st.user) and the [User object](https://docs.propelauth.com/reference/backend-apis/python#user) we get back from the function `get_user` found in our `propelauth.py` script.

The `st.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](https://docs.propelauth.com/overview/basics/organizations) they are members of as well as any [custom user properties](https://docs.propelauth.com/overview/user-management/user-properties#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.user` is very good at checking if the user is logged in or not. In this example, we'll use `st.user` to check if the user is authenticated. If not, we'll redirect the user to login via PropelAuth using Streamlit's [st.login](https://docs.streamlit.io/develop/api-reference/user/st.login) command.

```py
import streamlit as st
from propelauth import auth

if st.user.is_logged_in:
    user = auth.get_user(st.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:

If the user is logged in, we'll fetch the full [User object](https://docs.propelauth.com/reference/backend-apis/python#user) using `get_user` and then display the returned information.

## [Authorization / organizations](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#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](https://docs.propelauth.com/reference/backend-apis/python#user).

### Check Org Membership

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

```py
def return_org_name(org):
    return org.org_name

if st.user.is_logged_in:
    user = auth.get_user(st.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](https://docs.propelauth.com/reference/backend-apis/python#user) or [OrgMemberInfo](https://docs.propelauth.com/reference/backend-apis/python#org-member-info) 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.

```py
def return_org_name(org):
    return org.org_name

if st.user.is_logged_in:
    user = auth.get_user(st.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](https://docs.propelauth.com/reference/backend-apis/python#user) or [OrgMemberInfo](https://docs.propelauth.com/reference/backend-apis/python#org-member-info) 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.

```py
def return_org_name(org):
    return org.org_name

if st.user.is_logged_in:
    user = auth.get_user(st.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](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#calling-backend-apis)

As you may have noticed, this guide uses PropelAuth's [propelauth-py](https://docs.propelauth.com/reference/backend-apis/python) 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](https://docs.propelauth.com/reference/api/getting-started), allowing you to fetch members of an organization, create orgs, and a lot more.

```py
from propelauth import auth

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

## [Logging Out](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#logging-out)

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

```py
import streamlit as st
from propelauth import auth

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

It is not recommended to use Streamlit's [st.logout](https://docs.streamlit.io/develop/api-reference/user/st.logout) command as it will not completely log the user out of your application.

## [Logging Out Through The Hosted Pages](https://docs.propelauth.com/getting-started/additional-framework-guides/streamlit-authentication#logging-out-through-the-hosted-pages)

If you're using PropelAuth's [hosted pages](https://docs.propelauth.com/overview/basics/hosted-pages) you may find that the **Log Out** button found in the [Account Page](https://docs.propelauth.com/overview/basics/hosted-pages#user-account-page) Sidebar does not completely log the user out of their Streamlit session.

### 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.

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`:

```py
import streamlit as st
from propelauth import auth

if st.user.is_logged_in:
    auth.log_out(st.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.

Have any questions? Please reach out to [support@propelauth.com](mailto:support@propelauth.com).
