Replace Clerk with the Auth Worker
OS uses Clerk as its identity provider: user authentication, organization management,
session tokens, OAuth for MCP clients, and React components (SignIn, OrgSwitcher, UserButton).
We are replacing Clerk entirely with the Iterate Auth Worker (apps/auth), which already
provides user/org/project CRUD, OAuth server, session management, and client libraries.
Context#
Clerk is deeply integrated across ~20 files in apps/os: frontend components, middleware,
API auth, MCP OAuth, org membership queries, config sync scripts, and the permission model.
The auth worker (apps/auth) was built as a self-hosted replacement and already covers ~70%
of what Clerk provides: organizations, projects, memberships, invitations, roles, OAuth server,
JWT issuance, JWKS, and client libraries (createIterateAuth, createIterateAuthClient).
PR #1346 added project-scoped OAuth for MCP clients in the auth worker. The apps/mcp dummy
server proves the JWT validation pattern works. This ADR covers replacing Clerk for all of OS,
not just MCP.
Decision#
-
The auth worker is the identity authority. It owns Users, Organizations, Projects (identity + membership), and ID generation for these entities. OS is a consumer.
-
OS generates project TypeIDs and passes them to the auth worker. The auth app accepts an optional
idparameter on project creation. OS mintsproj__<env>__<suffix>TypeIDs and hands them in so it can derive Durable Object names and infrastructure from the ID before the auth record exists. The auth app stores the ID opaquely. -
Single MCP endpoint. Per-project MCP hostnames (
mcp__<slug>.iterate.app) are removed. This ADR originally placed the endpoint atos.iterate.com/mcp; the canonical endpoint later moved tomcp.iterate.com. The.well-known/oauth-protected-resourcemetadata points to the auth worker. -
One auth middleware, one principal model. A single middleware resolves any request (web cookie, MCP bearer, admin API secret) into a Principal (User or Admin). User principals carry an org and project list from the token. Admin principals have blanket access. All authorization goes through
principal.can(action, resource). -
OAuth with the auth worker for both web and MCP. Two OAuth client registrations: one for the web app (all orgs+projects included automatically, no selection screen), one for MCP clients (project selection screen shown, token scoped to selected projects). Both produce tokens with the same claims shape.
-
URL restructure.
/orgs/:slug/projects/:slugbecomes/projects/:slugfor the main project UI and/org/:slugfor org settings. Projects and orgs are orthogonal in the URL because multiple orgs can access the same project. -
No data migration. OS data is a POC. All existing data is destroyed.
-
Dev environments use the production auth worker with per-environment OAuth client registrations, same pattern as the current Clerk sync script.
Alternatives considered#
- Clerk for web, auth worker for MCP only: Keeps two identity systems, doubles the integration surface, forces eventual migration anyway.
- Auth worker as cookie-sharing proxy: Couples apps at the cookie layer, CSRF concerns, brittle cross-domain setup.
- Per-project MCP hostnames with auth worker OAuth: Still requires wildcard DNS/certs, complex host routing in the Durable Object, no benefit over a single endpoint.
Consequences#
- Clerk dependencies (
@clerk/backend,@clerk/mcp-tools,@clerk/tanstack-react-start) are fully removed fromapps/os. - The
sync-clerk-apps.tsscript is replaced with an equivalent that registers OAuth clients via the auth worker'sinternal.oauth.ensureClientendpoint. - CONTEXT.md terms "Clerk Organization", "Clerk User", "Clerk OAuth Token", "Clerk Session Token" are replaced with auth-worker-native equivalents.
- ADR 0001 (use Clerk as MCP OAuth server) is superseded.
Amendment: project ID minting (implemented reality)#
Decision 2 above did not survive contact with the implementation. The auth worker is the
canonical minter of project ids, in the format prj_<uuid> (generateId("prj") in
apps/auth/src/server/orpc/routers/_shared.ts). OS no longer mints project ids at all —
its operator/recovery minting path (src/domains/projects/project-id.ts) was deleted with
the legacy stack in the itx-v4 replacement; project creation always registers through the
auth worker. proj_ was a legacy OS-typeid prefix that is no longer minted anywhere.