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.

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_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().

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.2.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.2.0
File Size Uploaded Provenance
incorta_auth-0.2.0.tar.gz 206.7 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 243.3 kB

Release files / incorta_auth-0.2.0.tar.gz

Download URL incorta_auth-0.2.0.tar.gz
Size 206.7 kB
Tags Source
SHA-256 checksum
How to use checksums
32d97d853ea8c1ca7a07c54bb21849106468d5d0643097f4d28d96286f33c938
BLAKE2b-256 checksum
How to use checksums
2bb6b23cb2b55812b28d397ea2ac235d0f51854574d181d7862dff89bd5f9b0c
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.2.0-py3-none-any.whl

Download URL incorta_auth-0.2.0-py3-none-any.whl
Size 36.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a6dfc8901a15379b124325918969c2c65f81cb81aaf2e82fdab9f2b8ca7d0e96
BLAKE2b-256 checksum
How to use checksums
366f60e86e67095003421f8ce69c82a8f25f067bd6abfad3bb0eeada62b25276
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.2.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