Skip to main content

incorta-auth (Python)

PyPI version license

Authenticate Incorta users in Python apps. The Python port of @incorta/auth — it gates your app behind the real Incorta login (no custom login page) using the OAuth 2.0 / OIDC authorization server built into Incorta.

To read Incorta with the session this package holds — schemas, tables, columns, rows, all filtered to the signed-in user — add incorta-sdk, released from this repository at the same version.

Both SDKs implement one spec: the same OAuth authorization-code flow against {INCORTA_URL}/oauth/{tenant}, the same INCORTA_* configuration, and the same encrypted-cookie sealing (compact JWE, dir + A256GCM, key derived SHA-256("incorta-auth:" + secret)) — cookies sealed by either language unseal in the other, verified by fixtures generated with the TS implementation (tests/fixtures/).

Status

  • Core protocol layer: configuration, endpoint discovery with split-horizon support, authorization-code exchange, silent refresh with rotation, RS256/JWKS token verification, session sealing, and RFC 7591/7592 client registration as library functions.
  • ASGI / FastAPI adapter: IncortaAuthMiddleware gates any ASGI app behind Incorta login and serves /auth/*; incorta_auth.fastapi provides the get_session dependency. See examples/fastapi.
  • Streamlit adapter: incorta_auth.streamlit.gate() gates a Streamlit app (which has no routes and no response-cookie control) behind Incorta login. See examples/streamlit and the design notes below.

Usage (FastAPI / any ASGI app)

from fastapi import Depends, FastAPI
from incorta_auth import AuthSession
from incorta_auth.asgi import IncortaAuthMiddleware
from incorta_auth.fastapi import get_session

app = FastAPI()
app.add_middleware(IncortaAuthMiddleware)  # config from INCORTA_* env vars

@app.get("/api/me")
def me(session: AuthSession = Depends(get_session)):
    return {"user": session.user.sub, "roles": session.user.roles}
    # session.access_token → Authorization: Bearer for Incorta REST APIs

The middleware serves {base_path}/{login,callback,session,logout,signed-out} (+ /token when expose_access_token=True), attaches the session to request.state.incorta_auth, redirects signed-out browser navigations into Incorta login (relative Location — proxy-safe), 401s signed-out data requests, refuses signed-out websockets, and answers everything with an honest 503 while the INCORTA_* env is missing. public_paths=("/api/health",) exempts liveness probes from both the gate and the 503. For apps embedded in a cross-site iframe, spread embedded_cookie_config() into the middleware (SameSite=None + Partitioned).

Embedded (cross-site iframe) sign-in

When the app is previewed inside a cross-site iframe and the Incorta cluster has iframe inclusion off (the default — Incorta's login is X-Frame-Options: SAMEORIGIN and its session cookie is SameSite=Lax), a signed-out user can't complete Incorta login inside the frame. Pass embedded_sign_in="new-tab" (or INCORTA_EMBEDDED_SIGN_IN=new-tab) and the gate serves a small interstitial instead of a doomed in-frame redirect: at the top level it still redirects straight to Incorta; inside a frame it offers a "Sign in with Incorta" button that opens the app in a new top-level tab, where login works. The proper fix remains enabling iframe inclusion on the cluster — this is the graceful fallback when you can't.

Install

uv add incorta-auth        # or: pip install incorta-auth

Prerequisites

Same as the TS SDK (see the repo README): enable the authorization server on the cluster, then register an OAuth client. The registration CLI ships with the npm package —

npx incorta-auth register \
  --url http://localhost:8080/incorta \
  --tenant demo \
  --name "My App" \
  --redirect http://localhost:4000/auth/callback

— or use the library functions below from Python.

Usage (core)

Configuration comes from INCORTA_* env vars (INCORTA_URL, INCORTA_TENANT, INCORTA_CLIENT_ID, INCORTA_AUTH_SECRET, optional INCORTA_CLIENT_SECRET — only a confidential client has one; Incorta registers apps as public clients and the SDK uses PKCE — / INCORTA_APP_URL / INCORTA_INTERNAL_URL / INCORTA_COOKIE_SAMESITE / INCORTA_COOKIE_PARTITIONED) or explicit keyword arguments:

from incorta_auth import IncortaAuth

auth = IncortaAuth()  # reads INCORTA_* env vars

# Send the browser to Incorta login:
login = auth.begin_login(origin="http://localhost:4000", secure=False, redirect_to="/")
# → redirect to login.location, set login.set_cookies

# Complete the callback (?code&state):
result = auth.complete_callback(
    origin="http://localhost:4000", secure=False,
    cookie_header=request_cookie_header, query=dict(query_params),
)
# → redirect to result.location, set result.set_cookies

# Gate requests / read the user and token:
session = auth.get_session(request_cookie_header)
if session:
    session.user.sub, session.user.roles, session.access_token

# Refresh-aware load (rotates the refresh token, slides the session cookie):
loaded = auth.load_session(cookie_header=request_cookie_header, secure=False)

Client registration (RFC 7591/7592):

from incorta_auth import register_client, get_client, update_client_redirect_uris, delete_client

client = register_client(
    incorta_url="http://localhost:8080/incorta", tenant="demo",
    name="My App", redirect_uris=["http://localhost:4000/auth/callback"],
)

Usage (Streamlit)

import streamlit as st
import incorta_auth.streamlit as incorta

user = incorta.gate()        # first line of the entry script; st.stop()s until signed in
st.write(f"Hello {user.name}", user.roles)
incorta.access_token()        # Bearer for Incorta REST APIs (auto-refreshed)
if st.button("Sign out"):
    incorta.sign_out()        # interstitial — SSO would otherwise bounce right back in

The OAuth redirect_uri is the app root ({INCORTA_APP_URL}/) — register exactly that. Tokens live only in st.session_state; a hard refresh silently re-authenticates through Incorta SSO. Multi-page entry scripts can restore the originally requested page with pop_return_path().

The gate also honours the cross-adapter session cookie (incorta_auth_session, sealed under INCORTA_AUTH_SECRET exactly like the ASGI/TS adapters seal it): if the websocket's initial request carries a valid one, it seeds st.session_state once per browser session — no login round trip. Streamlit never writes that cookie itself; it is how the platform opens the app signed in as a given user (e.g. the app-card screenshot capture). Same trust as the other adapters: only the holder of the app secret can mint it. It never overrides an existing session and never undoes sign_out().

For an app embedded in a cross-site iframe on a cluster with iframe inclusion off, pass gate(embedded_sign_in="new-tab") (or INCORTA_EMBEDDED_SIGN_IN=new-tab): signed out, the gate shows a "Sign in with Incorta" button that opens the app in a new top-level tab (where login works) instead of bouncing the frame to an Incorta login it can't render — see the ASGI section's note above.

Streamlit design notes (browser-spike findings, 2026-07-20)

Streamlit cannot host a callback route, set response cookies, or (it turns out) navigate from component JS — the flow is built from the three vehicles a real-browser spike (Playwright, Streamlit 1.59) validated:

  1. Navigation: components.html iframes are sandboxed allow-same-origin allow-scripts … without allow-top-navigation, so JS cannot redirect the app. st.html strips both scripts and meta tags (DOMPurify). The one silent vehicle is a st.markdown(unsafe_allow_html=True) meta-refresh, which navigates the app frame itself — allowed everywhere, including inside iframes.
  2. State cookie: components.html JS can write cookies on the app origin (allow-same-origin + srcdoc). The sealed login state (state/nonce/return target, same JWE sealing as the TS SDK's state cookie) is written in two variants — SameSite=Lax for top-level tabs and SameSite=None; Secure; Partitioned for cross-site iframes (the Incorta builder preview; verified in a genuine cross-site embed) — and read back after the round-trip via st.context.cookies.
  3. Ordering: the cookie write reliably precedes the meta navigation (5/5 in the spike) — both are same-turn DOM operations and the srcdoc iframe has no network dependency. Binding is strict: a missing state cookie fails the callback with state_missing (the same failure a cookie-blocked browser gets from the TS SDK) and offers a retry button.

Compatibility note: the meta-refresh passthrough and the component sandbox flags are Streamlit implementation details — the full flow is pinned by a browser e2e and the AppTest suite, so a Streamlit upgrade that changes either fails loudly, not silently.

Development

uv sync
uv run pytest
uv run ruff check . && uv run ruff format --check .
uv run mypy

The test suite runs against an in-process replica of Incorta's authorization server (tests/mock_incorta.py, a port of the TS suite's mock-incorta.ts) served through httpx.MockTransport — one-time 60s codes, rotating refresh tokens, and split-horizon simulation included. Regenerate the cross-language sealing fixtures with:

node tests/fixtures/generate-fixtures.mjs > tests/fixtures/sealed-fixtures.json

Metadata

Release files for incorta-auth 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for incorta-auth 0.6.0
File Size Uploaded Provenance
incorta_auth-0.6.0.tar.gz 221.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for incorta-auth 0.6.0
File Interpreter ABI Platform
incorta_auth-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 267.8 kB

Release files / incorta_auth-0.6.0.tar.gz

Download URL incorta_auth-0.6.0.tar.gz
Size 221.7 kB
Tags Source
SHA-256 checksum
How to use checksums
8f42add08837c2dfd6cee9d52c943d9bde6b9ce252aafcb516cfa708293783b7
BLAKE2b-256 checksum
How to use checksums
abbf164c33a7da5467deffd4c726bce643b558ce7364e65a42fcbd450e455fbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / incorta_auth-0.6.0-py3-none-any.whl

Download URL incorta_auth-0.6.0-py3-none-any.whl
Size 46.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9c210123016fcd563a519b07056281e467ce53acb8dc08b8181e393394ea73f5
BLAKE2b-256 checksum
How to use checksums
1046e0a4509482f8970264eecb27e1ae3246d30eded5970738fac746d267b903
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for incorta-auth 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page