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
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:
- Access-control concept: https://tai42.ai/concepts/access-control
- Build an identity provider (author guide): https://tai42.ai/guides/authors/identity-provider
- Ecosystem catalog: https://tai42.ai/reference/catalog
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):
algmust be in the configured allowlist before any key lookup, soalg=noneand symmetric downgrades are rejected by construction.issmust match exactly,audmust contain the configured audience, andexpis 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ab868995b510739fca6cd02e3ef1452b4d5d5889958c597570e2012d3ad34b3
|
|
| MD5 |
39809dae32547f8ceac4873cd94524cd
|
|
| BLAKE2b-256 |
1c5cc2e8d932ed03b8f3ae69c5156b09d6bdbc9bc0d7210f512240cb1ef820f3
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dcea40d69003c08b5fe4c7c9a270e9a7cfb11b864e8951c1f99ad0aa925bcc5a
|
|
| MD5 |
be460857dcfb895a563327212aefa981
|
|
| BLAKE2b-256 |
6a981dc7df0677123a0880c31fa3f07086aca7ad94d1251391d956573546d4e5
|