Skip to main content

Validate-only OIDC identity provider for the TAI ecosystem: an installable plugin that registers itself as the "identity-oidc" identity provider and resolves an issuer-minted JWT to an authenticated identity (no key minting, no stored state).

Project description

tai42-identity-oidc

CI License: Apache 2.0

The validate-only OIDC identity provider for the TAI ecosystem — an installable plugin that registers itself as the "identity-oidc" identity provider and resolves an issuer-minted JWT presented on an API call to an authenticated identity.

Importing the package registers the provider in tai42-contract's module-level identity-provider registry (register_identity_provider("identity-oidc", ...)), with no tai42_app handle involved — so it registers in any process that imports it, including ones that never start(). A deployment selects it by including identity-oidc in the access-control auth_providers list.

Its only tai-* dependencies are tai42-contract (the identity ABCs and the registry it registers through) and tai42-kit[jwt] (the OIDC discovery / JWKS / JWT-verify helper, tai42_kit.net.jwt). 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 identity provider is how the runtime answers "who is this caller?": it resolves an inbound credential to an authenticated identity, and access control decides what that identity may do. This package is one such provider (issuer-minted OIDC JWTs, validate-only); 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:

Validate-only — no key minting, no stored state

This provider implements the base tai42_contract.access_control.identity.IdentityProvider ABC — deliberately not ApiKeyIdentityProvider. It mints no keys and holds no state: keys are managed at the external issuer, so the skeleton's key-minting surface refuses this provider loudly (Studio renders "keys are managed at the external issuer" rather than a raw error). It is the ecosystem's proof of the mint-capability gate.

How a token resolves

On each request the presented token runs a cheap structural gate first (looks_like_jwt — three dot-separated base64url segments), so sk-… keys and tai-sess-… session tokens fall through the provider chain with a clean None and no network fetch. A JWT-shaped token is verified against the issuer's JWKS (discovered once, cached with TTL and bounded unknown-kid refetch):

  • alg must be in the configured allowlist before any key lookup, so alg=none and symmetric downgrades are rejected by construction.
  • iss must match exactly, aud must contain the configured audience, and exp is required (with leeway).

A JWT-shaped token that fails verification raises — it is an attack or a misconfiguration, never someone else's credential — and the request denies (fail-closed). The verified claims become AuthIdentity.claims; the user id is the namespaced subject idp:{issuer}:{claim} (claim defaults to sub).

The idp:{issuer}: prefix fences issuer subjects into their own slice of the single flat policy user_id namespace they share with usr-* accounts, sk-* key ids, and oidc:* login subjects — so a trusted issuer emitting a sub equal to a privileged principal's id cannot inherit that principal's policy.

The reserved owner_user_id claim is stripped from the returned claims as defense-in-depth: it is authoritative only from the key-mint path, and the skeleton verifier's central strip already removes it for every non-mintable provider.

Deny-by-default

An issuer-authenticated subject with no operator-provisioned policy resolves to the empty AccessPolicy, which holds no scopes, so every protected route denies. There is no auto-provisioning and no default-policy path. Operators grant access by creating a policy for each expected subject id under its namespaced form — idp:{issuer}:{sub} — through the existing policy routes.

Chaining note (v1)

Chaining two JWT providers against different issuers is out of scope for v1: a single identity-oidc member verifies against one configured issuer.

Configuration

All config is the plugin's own TAI_IDENTITY_OIDC_* env namespace (the plugin never reads skeleton config):

Env var Required Default Meaning
TAI_IDENTITY_OIDC_ISSUER yes The OIDC issuer URL (discovery is fetched from {issuer}/.well-known/openid-configuration).
TAI_IDENTITY_OIDC_AUDIENCE yes The audience the token's aud must contain.
TAI_IDENTITY_OIDC_ALLOWED_ALGS no ["RS256"] JSON array — the signing-algorithm allowlist. Never "any".
TAI_IDENTITY_OIDC_CLAIM no sub The claim mapped into the namespaced user id.
TAI_IDENTITY_OIDC_JWKS_TTL_SECONDS no 3600 JWKS cache TTL.

A missing required setting raises a loud settings error at provider construction (boot).

Surface

The provider implements tai42_contract.access_control.identity.IdentityProvider:

Method Does
validate_token(token) Gates non-JWT tokens to None; verifies a JWT against the issuer's JWKS and returns the AuthIdentity with the namespaced user id. A verification failure raises (fail-closed).
healthcheck() Fetches the issuer's discovery + JWKS at boot; raises loudly if the issuer is unreachable or the documents are broken.

readiness_targets() inherits the base empty tuple — the issuer is not a kit-pooled client, so the boot healthcheck() is the reachability check.

Requirements

Requires Python 3.13+ and a reachable OIDC issuer publishing standard discovery + JWKS. An unreachable or broken issuer is caught loudly by healthcheck() at startup rather than failing per-request.

Install

Nothing is on PyPI yet, so install from source — clone this repo and add it as an editable dependency of the environment that runs the server:

git clone https://github.com/tai42ai/tai-identity-oidc
cd tai-skeleton   # or your own app checkout
uv add --editable ../tai-identity-oidc    # once published: uv add tai42-identity-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_identity_oidc-0.1.1.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

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

tai42_identity_oidc-0.1.1-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: tai42_identity_oidc-0.1.1.tar.gz
  • Upload date:
  • Size: 14.5 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_identity_oidc-0.1.1.tar.gz
Algorithm Hash digest
SHA256 5ab868995b510739fca6cd02e3ef1452b4d5d5889958c597570e2012d3ad34b3
MD5 39809dae32547f8ceac4873cd94524cd
BLAKE2b-256 1c5cc2e8d932ed03b8f3ae69c5156b09d6bdbc9bc0d7210f512240cb1ef820f3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tai42_identity_oidc-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.4 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_identity_oidc-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dcea40d69003c08b5fe4c7c9a270e9a7cfb11b864e8951c1f99ad0aa925bcc5a
MD5 be460857dcfb895a563327212aefa981
BLAKE2b-256 6a981dc7df0677123a0880c31fa3f07086aca7ad94d1251391d956573546d4e5

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