The two ways to do it

There are two places the sign-in can live.

  • Inside the app. Streamlit has built-in OpenID Connect login. The app itself sends people to Okta.
  • In front of the app. A proxy or load balancer handles sign-in, and the app only ever sees requests from people who passed.

The first is quicker for one app. The second is what most IT teams prefer once there are several.

Option 1: Streamlit’s built-in login

Streamlit documents this in its authentication guide. The steps below follow that guide.

In Okta

  1. Create an app integration of type OIDC, Web Application.
  2. Set the sign-in redirect URI to your app’s address followed by /oauth2callback.
  3. Assign the integration to the group that should have access, and to nobody else.
  4. Copy the client ID and client secret.

Step 3 is the access rule. Okta refuses sign-in to people who are not assigned to the integration.

In the app

Install Streamlit with its auth extra. The docs state that Authlib 1.3.2 or later is required.

pip install "streamlit[auth]"

Add an [auth] section to .streamlit/secrets.toml:

[auth]
redirect_uri = "https://dashboard.example.com/oauth2callback"
cookie_secret = "a-long-random-string"
client_id = "from-okta"
client_secret = "from-okta"
server_metadata_url = "https://your-subdomain.okta.com/.well-known/openid-configuration"

Then stop the app from rendering until the person has signed in:

import streamlit as st

if not st.user.is_logged_in:
    st.button("Log in with Okta", on_click=st.login)
    st.stop()

st.write(f"Signed in as {st.user.name}")

Ask Claude Code to put that check at the top of every page of the app, not only the first.

Things to know

  • The sign-in lasts 30 days. Streamlit’s docs say the identity cookie expires after 30 days, that this is not configurable, and that it is not tied to the expiry of the Okta token. A person removed from the group can keep an open session until the cookie expires, unless the app checks for itself.
  • Identity is read at the start of a session. st.user does not update while a session is open.
  • Group checks inside the app need testing. Streamlit surfaces attributes from the identity token in st.user. If you add a groups claim in Okta and want the app to check it, confirm that the claim arrives before you rely on it.
  • It does not work for embedded apps. The docs say authentication is not supported when the app is embedded in another page.

Option 2: sign-in in front of the app

Here the app has no login code. Something in front of it does the work.

  • A reverse proxy such as oauth2-proxy runs beside the app, sends people to Okta, and passes only signed-in requests through.
  • A load balancer can do the same. Several cloud load balancers can authenticate against an OIDC provider before forwarding a request.
  • An access service such as Cloudflare Access puts a sign-in page in front of a hostname.

The advantage is that the rule lives in one place and applies to every app behind it, including apps whose author forgot to add a check. The cost is that someone has to run the proxy, the certificates and the DNS.

What neither option solves

Sign-in answers “who can open this”. IT will ask four more things.

  1. Where is the database password? In Option 1 the Okta client secret sits in a file next to the code, usually beside the warehouse credential. The builder holds both.
  2. Who owns this app? Each app is its own Okta integration, its own host and its own secrets file. Nothing lists them.
  3. What happens when the builder leaves? The app keeps running under credentials only they knew.
  4. What did it ship with? Nobody scanned the code for leaked keys or vulnerable packages.

A single app can live with those. A company where dozens of people build apps cannot, and that is the point at which a shared place to run them is worth having. A written rule helps too, and there is a policy template you can adapt.

Where intrium fits

intrium is being built so that the builder runs one command and the rest is already decided: the URL opens only to the Okta or Google group IT chose, the app reads Postgres or Snowflake through a read-only credential the builder never sees, and the app appears in a catalog with a named owner. It is pre-launch, so today the two options above are what you can use.