Skip to main content

OIDC/OAuth2 login accounts provider for the TAI ecosystem: an installable plugin that registers itself as the "accounts-oidc" accounts provider (session-based login via an external identity provider, sessions in Redis, SSO code hand-back).

Project description

tai42-accounts-oidc

CI License: Apache 2.0

The OIDC / OAuth2 login accounts provider for the TAI ecosystem — an installable plugin that lets humans sign in through an external identity provider (Auth0, Google, Okta, Keycloak, Microsoft Entra, or GitHub) and mints opaque tai-sess-… session tokens backed by Redis.

Importing the package registers the provider in tai42-contract's module-level accounts registry via register_accounts_provider("accounts-oidc", …). That one call ALSO lands the factory in the identity registry under the same name — an accounts provider IS the token answerer for its own sessions. No tai42_app handle is involved, so it registers in any process that imports it. A deployment selects it by including accounts-oidc in the access-control auth_providers list; the plugin never inserts itself.

Its only tai-* dependencies are tai42-contract (the accounts ABC, the login-method models, and the dual-registry registration) and tai42-kit (the Redis client, the session-token hash, and the OIDC/JWT helper). It never imports the skeleton — the plugin is contract-facing, and the import is banned by ruff.

The TAI ecosystem

TAI is an open-source runtime for MCP tools, agents, and workflows. An accounts provider owns human sign-in: it authenticates a person, mints the session token their browser carries, and answers that token back as an identity on every later call. This package is one such provider (OIDC / OAuth2 login with Redis-backed sessions); any package can back the same contract, so this repo is this provider's own full doc home, and the documentation site covers the platform-level story:

The login flow

Browser → GET  /api/login/oidc/{provider}/authorize  → 302 issuer (state + PKCE + nonce)
Issuer  → GET  /api/login/oidc/{provider}/callback   → code→token exchange,
                                                        id_token verified (tai42-kit),
                                                        session minted (Redis),
                                                        302 /login?sso={code}
SPA     → POST /api/login/sso/exchange {code}         → {token, user_id} (once)
SPA     → any request with the token                  → validate_token

The OAuth callback is a top-level browser navigation, so the session token is never handed back in a URL. The callback mints the session, stores a one-time SSO code in Redis (acc:oidc:sso:{code}, 30 s TTL, single-use GETDEL), and 302s to /login?sso={code}; the SPA POSTs that code to /api/login/sso/exchange and receives the frozen {"data": {"token": …, "user_id": …}} shape exactly once.

Both the OAuth redirect_uri and the /login?sso= hand-back are derived from TAI_ACCOUNTS_OIDC_PUBLIC_BASE_URLnever the inbound request's Host/origin, so a Host-header-injecting client cannot steer where the auth code or hand-back land. The state parameter is HMAC-signed (state_key) AND stored server-side in Redis with the PKCE verifier and nonce (600 s TTL, single-use GETDEL). PKCE is S256, always.

Configuration

All config is the plugin's own TAI_ACCOUNTS_OIDC_* env namespace.

Env var Required Meaning
TAI_ACCOUNTS_OIDC_PROVIDERS yes (JSON) The list of configured login providers (see below).
TAI_ACCOUNTS_OIDC_STATE_KEY yes HMAC key for the tamper-evident OAuth state parameter.
TAI_ACCOUNTS_OIDC_PUBLIC_BASE_URL yes The public origin the flow returns to (e.g. https://studio.example.com). https required (plain http only for loopback).
TAI_ACCOUNTS_OIDC_SESSION_IDLE_SECONDS no (86400) Sliding idle window; a session dies once unused this long.
TAI_ACCOUNTS_OIDC_SESSION_ABSOLUTE_SECONDS no (2592000) Hard cap from mint, never extended by activity.

A missing STATE_KEY or PUBLIC_BASE_URL raises a ValueError naming the env var at provider construction (boot), never a silent default.

TAI_ACCOUNTS_OIDC_PROVIDERS is a JSON array; each row:

[
  {
    "name": "google",
    "preset": "google",
    "client_id": "…apps.googleusercontent.com",
    "client_secret": "…"
  },
  {
    "name": "corp",
    "preset": "okta",
    "issuer": "https://corp.okta.com",
    "client_id": "…",
    "client_secret": "…",
    "scopes": ["openid", "email", "profile"],
    "claim": "sub",
    "display": { "label": "Sign in with Corp SSO", "icon": null }
  }
]
  • name — URL-safe slug (^[a-z0-9-]+$), unique across the list (a duplicate raises at settings load).
  • preset — one of auth0, google, okta, keycloak, azure, github, or omit it for a raw OIDC issuer. A preset fills the button label and, for google, the fixed issuer.
  • issuer — the OIDC issuer URL. Required unless a preset fixes it (google); the per-tenant presets (auth0/okta/keycloak/azure) and a raw row require it, and a row missing its required issuer raises at settings load.
  • client_id / client_secret — the OAuth client credentials.
  • scopes — default ["openid", "email", "profile"].
  • claim — the id_token (or GitHub userinfo) claim mapped into the user id; default sub (GitHub defaults to id).
  • display.label / display.icon — button text (a preset provides a default) and an optional operator-supplied inline SVG. No brand marks ship in this repo.

Preset table

Preset Kind Issuer Default label
auth0 OIDC required (per-tenant) Continue with Auth0
google OIDC fixed https://accounts.google.com Continue with Google
okta OIDC required (per-tenant) Continue with Okta
keycloak OIDC required (per-tenant) Continue with Keycloak
azure OIDC required (per-tenant) Continue with Microsoft
github OAuth2 n/a (fixed endpoints) Continue with GitHub

github is the plain-OAuth2 path: no OIDC discovery, no id_token. It uses GitHub's fixed authorize/token endpoints and identifies the caller via GET /user, mapping by the numeric id claim; user ids resolve as oidc:github:{id}.

Identities and access

A caller authenticated through provider {name} resolves to the user id oidc:{name}:{claim}. There is no auto-provisioning. An issuer-authenticated subject with no pre-provisioned policy resolves to the empty access policy — every protected route denies. Operators grant access by creating a policy per expected subject id through the existing policy routes. This is the only v1 behavior; the plugin ships no default-role or auto-policy path.

The session record — and therefore the identity's claims — holds ONLY plugin-minted fields (user_id, created_at, absolute_deadline); the issuer's JWT claims are used at the callback to derive the user id and are never carried into the identity, so no external owner_user_id can reach the backend attenuation through this member.

Sessions

Sessions are Redis TTL records at acc:oidc:sess:{sha256(token)}. Idle expiry rides the Redis TTL (slid on each successful validation); the absolute deadline is an independent hard cap enforced in validate_token. Only the SHA-256 hash of the token is stored.

Running both tai-sess- accounts members

If a deployment installs BOTH this provider and tai42-accounts-postgres, they share the tai-sess- prefix and can only tell "not mine" from "mine" by a store lookup. Because a provider error propagates (never falls through to a later provider — correct for security), a store outage in an EARLIER auth_providers member fail-CLOSES a LATER member's sessions: an accounts-oidc (Redis-backed) session presented while an earlier accounts-postgres is down hits the Postgres provider first, raises, and denies — even though that token belongs to the healthy provider. This is fail-closed (safe) and inherent to the shared prefix; an operator running both members should know an earlier member's backend health gates later members' logins.

Requirements

Requires Python 3.13+, any plain Redis, and reachable OIDC issuers. An unreachable issuer or Redis is caught loudly by healthcheck() at startup rather than failing per-request. Chaining two different JWT-issuer providers against distinct issuers is out of scope for v1.

Install

Nothing is on PyPI yet, so install from source:

git clone https://github.com/tai42ai/tai-accounts-oidc
cd tai-skeleton   # or your own app checkout
uv add --editable ../tai-accounts-oidc    # once published: uv add tai42-accounts-oidc

Development

uv sync --extra dev
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest

[tool.uv.sources] resolves tai42-contract and tai42-kit from sibling checkouts for local development; the published wheel floors them from the index.

License

Apache-2.0. See LICENSE and NOTICE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tai42_accounts_oidc-0.1.1.tar.gz (26.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tai42_accounts_oidc-0.1.1-py3-none-any.whl (25.3 kB view details)

Uploaded Python 3

File details

Details for the file tai42_accounts_oidc-0.1.1.tar.gz.

File metadata

  • Download URL: tai42_accounts_oidc-0.1.1.tar.gz
  • Upload date:
  • Size: 26.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tai42_accounts_oidc-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9c5982ab386a5afd67109aa305ab04da7c1d71283541fa5765f34907f16ee18e
MD5 c2fda8dd54e9528d8e4661799aa0d34c
BLAKE2b-256 9eae063710089b8cb51fc872c872a899e9702c1f8b54680203fec8ec0d00a4d9

See more details on using hashes here.

File details

Details for the file tai42_accounts_oidc-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: tai42_accounts_oidc-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 25.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for tai42_accounts_oidc-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 07ba957e4804dc8fb0e1c688abc79938fea8e1b017591f080fea8854439c0e70
MD5 2aaeac88b60da4dec169ed0cd4348716
BLAKE2b-256 462cc9aaf5459ed272e17f65226d4fd4a74b96bfbf5907fd4ebb26a7f1204c16

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page