Skip to main content

Toolframe OIDC Auth

Use one OIDC identity for Dashboard sign-in and separate, revocable OAuth grants for third-party agents. Authlib implements the upstream OIDC client and downstream OAuth authorization server. Toolframe Auth owns local users, browser sessions and permission grants; MCP Auth consumes the shared token-verification service.

This plugin requires SDK 0.3.0, Core 0.6.1, Auth 0.2.0, Dashboard Auth 0.1.3 and MCP Auth 0.2.0. The OIDC plugin is optional and is not enabled by the Entry Register template.

Quick start

  1. Add the package to a running application, leaving it disabled until configured: toolframe --home APP plugin add pypi:toolframe-oidc-auth==0.2.0.
  2. Install compatible Core, Auth and Dashboard packages. For an existing Auth v1 store, back it up with its retained secrets, stage the new Auth wheel with toolframe --home APP plugin update auth --source pypi:toolframe-auth==0.2.0, inspect toolframe --home APP migrate plan auth, then apply that returned plan using toolframe --home APP migrate apply auth --plan-id PLAN_ID. Migration v2 only adds user, external-identity, session and agent-grant tables.
  3. Register a confidential web client at the upstream identity provider, with the exact callback https://app.example/_toolframe/oidc/callback/google. Store its client secret in a regular file readable only by the application owner (chmod 600); use an absolute path in configuration.
  4. Add the following desired-state settings to APP/toolframe.toml, preserving the application's existing ID, plugin declarations and storage configuration. Replace example values, then run toolframe --home APP apply.
[application]
# Keep the existing application ID and other settings.
public_url = "https://app.example"

[access]
mode = "protected"
failure_mode = "closed"
auth_provider = "auth"
dashboard_auth = "dashboard-auth"
oidc_provider = "oidc-auth"

[mcp_access]
mode = "protected"
auth_provider = "mcp-auth"

[plugins.auth.config]
identity_provider = "oidc-auth"
federated_identities = [
  { issuer = "https://accounts.google.com", subject = "VERIFIED_GOOGLE_SUB", label = "Alice", scopes = ["entry-register.read"] },
]

[plugins.oidc-auth]
source = "pypi:toolframe-oidc-auth==0.2.0"
enabled = true

[plugins.oidc-auth.config]
public_url = "https://app.example"
scopes = ["entry-register.read"]
dynamic_registration = true
max_clients = 1000
access_token_seconds = 300
grant_seconds = 2592000

[plugins.oidc-auth.config.providers.google]
label = "Google"
issuer = "https://accounts.google.com"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
client_id = "GOOGLE_CLIENT_ID"
client_secret_file = "/absolute/private/google-client-secret"

The auth, dashboard-auth, mcp-auth and SQLite provider must be enabled in the existing composition. Set Core's application.public_url and this plugin's public_url identically. Terminate HTTPS at a reverse proxy and preserve the canonical Host. Core never derives its issuer from forwarded headers. Only explicit OAuth routes allow cross-origin entry; Dashboard management and consent submission remain same-origin. Public-host requests cannot inherit proxy-loopback operator privileges. Configure proxy request limits for publicly exposed login and dynamic-registration endpoints.

The login page offers Continue with Google alongside Access Password login. Admission uses an administrator-verified issuer + subject, never an email address. The allowlist creates a local user on first successful login. Later changes belong to User access or the protected user-management API; changing that initial-admission entry does not overwrite an existing user. Unlisted identities are rejected. A user needs the relevant business scopes before they can delegate them to an agent. system.auth.users grants scoped user management; each federated user receives system.auth.self for their own connections.

Google, Keycloak and TinyAuth

Upstream providers share the same Authlib adapter; they do not need separate Toolframe authentication implementations. Use Keycloak as an identity broker by setting its realm issuer and discovery URL and registering the same Toolframe callback under a chosen provider ID, for example keycloak. Google can then be configured as an upstream provider within Keycloak. Toolframe still owns local permissions and issues the app-specific access token.

Each provider accepts issuer, server_metadata_url, client_id, client_secret_file, optional label, optional ca_file, and token_endpoint_auth_method (client_secret_basic, the default, or client_secret_post). HTTPS certificate validation stays enabled. ca_file adds a private CA for a controlled deployment; client secrets and IdP tokens are never included in browser presentation or the configuration journal. A changed secret file requires plugin reload. Provider metadata is discovered lazily, so an IdP outage does not prevent plugin startup or invalidate existing local sessions.

See the TinyAuth Docker fixture for reproducible, local TLS, user and client provisioning. Real acceptance covers TinyAuth; Google and Keycloak use the documented generic path but need deployment-specific live verification. Google OIDC documentation, Keycloak identity brokering, Authlib Starlette OIDC client and Authlib authorization server describe the upstream protocols and library integration.

Connect an agent

Give an OAuth-capable MCP client https://app.example/mcp. A protected request returns a Bearer challenge pointing at protected-resource metadata. The client can discover the authorization server, register, request consent and exchange an authorization code using PKCE S256. Consent displays the client ID, name, redirect URI, target resource and requested scopes; display names are unverified client metadata. If necessary, the user signs in at the upstream provider. Dashboard and Agent flows resolve to the same local user, with independent credentials.

OAuth endpoints are /oauth/authorize, /oauth/token, /oauth/register (when enabled) and /oauth/revoke. Authorization-server metadata is /.well-known/oauth-authorization-server/oauth; resource metadata is /.well-known/oauth-protected-resource/mcp. Both authorization and token requests must include resource=https://app.example/mcp. This is the default allowed resource; resources can declare additional exact URLs for custom consumers of the generic verifier. Core currently integrates that verifier with MCP only.

This release supports public clients, authorization-code + S256, refresh-token rotation, static registration and bounded RFC 7591 dynamic registration. An omitted registration authentication method selects none, explicitly returned in the registration response. Explicit secret-based client authentication is rejected. No wildcard redirects are accepted: redirects must exactly match an HTTPS URL or an HTTP loopback URL. To disable DCR, use:

[plugins.oidc-auth.config]
# Include the other required settings from above.
dynamic_registration = false
clients = [
  { client_id = "desktop-agent", client_name = "Desktop agent", redirect_uris = ["http://127.0.0.1:8347/callback"] },
]

Advertise only the business scopes you intend to expose through this issuer. Some MCP clients request every scope in resource metadata. Consent always shows the actual requested set. System permissions cannot be delegated. Client ID Metadata Documents (CIMD), confidential downstream clients, device grants, client-credentials grants, SAML, downstream OIDC ID tokens and automatic account linking are outside this release. An upstream ID/access token is never accepted as an app bearer token. Existing tf_mcp_ keys remain usable alongside OAuth; an unavailable optional OAuth plugin does not disable key verification.

Credentials, revocation and backup

OAuth access tokens last 300 seconds by default (30–900 configurable); grants last 30 days by default (60 seconds–one year configurable). Tokens use 256 random bits and only digests are persisted. Codes expire after 60 seconds and are single use. Refresh tokens rotate, cannot outlive the grant, and replay revokes the whole grant. Every MCP admission checks token expiry, resource binding, current user permissions and grant status. Already admitted operations may finish.

Connected agents lets the user revoke a connection without signing out of Dashboard. Disabling the user invalidates sessions and revokes all their grants. Logout ends the local browser session; it does not globally log out the IdP or revoke agent grants. Permission reductions take effect on subsequent admissions. OAuth authorization is explicit on each new flow; there is no implicit consent based solely on an upstream login.

The plugin persists encrypted, browser-bound ten-minute authorization flows and code/token digests in its managed store. Pending flows are capped at 1000 and expired flows are removed when new flows are created. Client registrations are persistent and bounded by max_clients; they are not automatically deleted. Token and grant history currently requires operator-managed retention. Back up data/plugins/auth/data.sqlite, data/plugins/oidc-auth/data.sqlite, retained .toolframe/secrets/, composition, and referenced upstream secret files together, using consistent SQLite backups. Keep the oidc-auth/flow-key.key secret across reloads/restarts. A missing key with an existing store blocks activation; restore it from backup.

Development checks

With matching SDK, Auth and SQLite packages installed in a Python 3.14 venv:

uv pip install --python .venv/bin/python --find-links ../wheelhouse -e .
uv pip install --python .venv/bin/python pytest pytest-asyncio ruff
.venv/bin/pytest -q
.venv/bin/ruff check src tests examples

Tests use real managed SQLite and Authlib grants. They cover PKCE rejection, resource binding, one-use codes, refresh rotation/replay, browser-bound consent, explicit denial, client registration and revocation. Core's coordinated suite covers real workers, discovery, proxy authority and optional-provider outages; the fixture script exercises an installed app through real FastMCP OAuth.

For release validation and Trusted Publisher setup, see releasing.

License

Apache-2.0. See LICENSE and NOTICE.

PostgreSQL storage

Version 0.2 requires SDK 0.3 and declares PostgreSQL migration SQL alongside the unchanged SQLite history. Provision this plugin’s stable store owner with the PostgreSQL provider before enabling it. The application still runs one Core; retain its private keys and configuration with the application home. Existing SQLite records are not imported automatically.

Metadata

Release files for toolframe-oidc-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 toolframe-oidc-auth 0.2.0
File Size Uploaded
toolframe_oidc_auth-0.2.0.tar.gz 88.9 kB Details

Built distribution (wheel)

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

Total release size: 112.7 kB

Release files / toolframe_oidc_auth-0.2.0.tar.gz

Download URL toolframe_oidc_auth-0.2.0.tar.gz
Size 88.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7997a0305e09c61aa5abb846f9c066af9942df048300cf35b01bef25e1a250c5
BLAKE2b-256 checksum
How to use checksums
6412636717e4a8ce7dab16b82789337828aabce85e0e90cd5d719daa4a27b2c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / toolframe_oidc_auth-0.2.0-py3-none-any.whl

Download URL toolframe_oidc_auth-0.2.0-py3-none-any.whl
Size 23.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0217f904acc9e60dc0534b5d6edb16b3bf39fb1117b38417a6ca0809f79e03d1
BLAKE2b-256 checksum
How to use checksums
3431194348c02f08158de2a83c9fa986fa6eb19edafcc95e926437f1d273f46e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

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