Skip to content

Phase 6: dedicated auth worker — RFC 7591 DCR + RFC 8414 metadata + JWKS rotation, all via iii primitives #46

@rohitg00

Description

@rohitg00

Phase 6: dedicated auth worker — RFC 7591 DCR + RFC 8414 metadata + JWKS rotation, all via iii primitives

Tracks an additive follow-up to the v0.4.0 overhaul (#45). Targets v0.5.0.

Why

Phase 1 of #45 delegates exposure to iii-worker-manager RBAC via auth_function_id. That's the hook. It is currently empty — every deploy must write its own auth function from scratch.

The MCP + A2A specs both lean on the same OAuth 2.1 primitives:

  • RFC 7591 — Dynamic Client Registration. Clients onboard at runtime via POST /register. Without it, every Claude Desktop / Cursor / IDE that hits a federated MCP needs an admin to register manually at each server. Doesn't scale past ~5 servers. Anthropic's MCP marketplace flow assumes DCR.
  • RFC 8414 — Authorization Server Metadata. /.well-known/oauth-authorization-server. Tells clients which scopes / grant types / PKCE methods the IdP supports. We already cover the resource-server side (RFC 9728); 8414 is the auth-server side. Both required for full client discovery.
  • JWKS rotation, token introspection, IdP capability matrix (Okta / Auth0 / Entra / Google / Keycloak / Ping / ForgeRock). Builders pick IdP first, write code second. Without this content next to the worker, they wire Entra-backed MCP, hit the 7591 wall in prod, rip out + redo.

Current state: each MCP/A2A deploy reimplements this stack, usually as FastAPI middleware.

Iron rule: everything through iii primitives. No FastAPI middleware bolted on the side. Auth becomes a worker the same way every other capability is a worker.

Surface

New crate iii-hq/workers/auth/. Sibling shape to introspect/ and llm-router/ (~1500-2000 LOC).

Functions registered:

Function ID Trigger Purpose RFC
auth::validate (called via iii.trigger by iii-worker-manager) Token introspection — returns AuthResult { allowed_functions, forbidden_functions, context }
auth::server_metadata HTTP GET /.well-known/oauth-authorization-server Authorization server discovery doc 8414
auth::resource_metadata HTTP GET /.well-known/oauth-protected-resource Resource server discovery doc 9728
auth::register HTTP POST /register Dynamic Client Registration 7591
auth::jwks HTTP GET /.well-known/jwks.json Public key set
auth::jwks_rotate cron (e.g. daily) Generate new keypair, retire old, update state::set
auth::token HTTP POST /token Token issuance + refresh + introspection 6749 / 7662

State scopes:

  • auth:clients — registered clients, indexed by client_id. Survives worker restart.
  • auth:jwks — current + previous keypairs (rotation overlap window).
  • auth:tokens — refresh tokens + revocation list.

CLI flags:

--engine-url <URL>
--issuer <URL>           # public base URL advertised in metadata
--idp-mode <KIND>        # local | bridge:keycloak | bridge:okta | bridge:auth0 | bridge:entra
                         # local issues + verifies own JWTs; bridge: federates to external IdP
--rotation-cron <EXPR>   # default "0 3 * * *" (daily 3am UTC)
--debug

Wiring into mcp + a2a

No code changes in the protocol workers. The deploy authors config.yaml:

workers:
  - name: iii-worker-manager
    config:
      rbac:
        auth_function_id: auth::validate
        expose_functions:
          - metadata:
              public: true
  - name: iii-auth
    config:
      issuer: https://api.acme.dev
      idp-mode: bridge:keycloak
  - name: iii-mcp     # served as POST /mcp
  - name: iii-a2a     # served as GET /.well-known/agent-card.json + POST /a2a

Now every tools/call (MCP) and message/send (A2A) flows through auth::validate. JWKS rotation is automatic. DCR endpoint is live. Auth-server metadata is discoverable. None of this lives in the mcp or a2a crate.

IdP capability matrix

Document in auth/README.md next to the worker. Initial roster (from Posta's research):

IdP RFC 7591 DCR RFC 8414 metadata PKCE Notes
Keycloak yes yes required reference IdP for bridge:keycloak; full demo in README
Okta yes yes required bridge config straightforward
Auth0 yes yes required bridge config straightforward
Entra ID no yes required DCR not supported — clients must be pre-registered manually
Google no yes required DCR not supported
Ping yes yes required bridge config
ForgeRock yes yes required bridge config

Builders read this BEFORE picking. Saves the rip-and-redo cycle.

Differentiation vs incumbents

Posta ships FastAPI middleware. iii ships a worker that any protocol bridge consumes. mcp/a2a stay narrow protocol transports — auth doesn't pollute their crates. JWKS rotation is a cron trigger, not a thread you have to remember to start. DCR is a function any worker can override (e.g. inject domain validation) by registering auth::register with higher priority. That last move is impossible in middleware-shaped frameworks.

Out of scope

  • mTLS / client cert flows.
  • SAML / WS-Federation.
  • Per-tenant key isolation (single-tenant for v0.5.0; multi-tenant tracked separately).
  • Token issuance for non-OAuth flows.

Sequencing

Lands as v0.5.0 after #45 ships v0.4.0. No dependency on Phase 2c (Streamable HTTP SSE) — auth works against the existing Authorization header pathway today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions