incorta-auth (Python)
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.
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:
IncortaAuthMiddlewaregates any ASGI app behind Incorta login and serves/auth/*;incorta_auth.fastapiprovides theget_sessiondependency. Seeexamples/fastapi. - Streamlit adapter:
incorta_auth.streamlit.gate()gates a Streamlit app (which has no routes and no response-cookie control) behind Incorta login. Seeexamples/streamlitand 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).
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_CLIENT_SECRET,
INCORTA_AUTH_SECRET, optional 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().
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:
- Navigation:
components.htmliframes are sandboxedallow-same-origin allow-scripts …withoutallow-top-navigation, so JS cannot redirect the app.st.htmlstrips both scripts and meta tags (DOMPurify). The one silent vehicle is ast.markdown(unsafe_allow_html=True)meta-refresh, which navigates the app frame itself — allowed everywhere, including inside iframes. - State cookie:
components.htmlJS 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=Laxfor top-level tabs andSameSite=None; Secure; Partitionedfor cross-site iframes (the Incorta builder preview; verified in a genuine cross-site embed) — and read back after the round-trip viast.context.cookies. - 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.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| incorta_auth-0.1.0.tar.gz | 203.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| incorta_auth-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 237.8 kB
Release files / incorta_auth-0.1.0.tar.gz
| Download URL | incorta_auth-0.1.0.tar.gz |
|---|---|
| Size | 203.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7a0e4694f95991dceef6b935ada9492b9d8f79a9365cefa231b7cce2fca81aac
|
|
BLAKE2b-256 checksum How to use checksums |
8de65c11aa7b1c64155e5cff5d038912e77ce1fedd437d2aaf62f2a144f7946f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","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.1.0-py3-none-any.whl
| Download URL | incorta_auth-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
282205c1d10d6d2adcdb6a6b4faf571708f65ff568b8d2c0419d10ee7ae046f7
|
|
BLAKE2b-256 checksum How to use checksums |
118b533e77caf7fbcd44b01a071448d1dd0fd5fbba4483e4bd8c83e1147cd253
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","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.1.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.