# 0003: Convex Auth v2 as the shared identity plane

https://exo.now/docs/adr/0003-convex-auth-v2-identity-plane

> Status: accepted · Date: 2026-09-23



# ADR 0003: Convex Auth v2 as the shared identity plane [#adr-0003-convex-auth-v2-as-the-shared-identity-plane]

* Status: accepted
* Date: 2026-09-23
* Supersedes: [0002-clerk-identity-boundary.md](0002-clerk-identity-boundary.md)
* Amended by: [0004-exo-id-central-identity-provider.md](0004-exo-id-central-identity-provider.md) — Exo is the sole OAuth client and identity provider; apps are relying parties.

## Context [#context]

ADR 0002 chose Clerk as the authentication provider for the Exo identity plane, with Exo
retaining authorization. Since then, two things changed:

1. Convex Auth reached a v2 architecture (components: a core that mints RS256 JWTs and owns
   sessions, plus provider components for password/username, OAuth, passkeys, email). Per the
   Convex team it is stable enough for deployment now and reaches GA by Q4; Interspace product
   scale stays small until then, so preview risk is acceptable.
2. Clerk charges per MAU and makes a proprietary vendor the default login surface for every
   Exo-owned app, while all of our app state already lives in Convex. Keeping identity in
   the same system that owns the data removes a vendor, a webhook surface, and a whole
   identity-mapping layer.

Rampart shipped Convex Auth v2 with Google and Apple OAuth in September 2026 and is the
reference implementation for the platform pattern.

## Decision [#decision]

Convex Auth v2 is the shared identity plane for every Interspace application. Exo-owned
apps use it for authentication; Exo retains authorization exactly as before (resource-local
grants, roles, capabilities, validity, revocation).

The platform pattern, proven in Rampart:

* ~~Mount the auth **core component** (`httpPrefix: "/auth"`, `AUTH_PRIVATE_KEY`/`AUTH_JWKS`
  env) plus whichever **provider components** the app needs (password/username, OAuth
  Google/Apple, passkeys) in the app's `convex/convex.config.ts`.~~ &#x2A;*Superseded by [ADR 0004](0004-exo-id-central-identity-provider.md):** only Exo mounts the
  core and provider components; apps verify Exo ID tokens with `customJwt` and mount none.
* `convex/auth.config.ts` verifies core JWTs as `customJwt` against the deployment JWKS at
  `${CONVEX_SITE_URL}/auth/.well-known/jwks.json`.
* Each provider wires `setup*` with `attachUserCallbacks({ createUser })`; the app owns its
  `users` table and the profile mapping. Provider components own accounts/sessions.
* Clients use `@convex-dev/auth/react` (`ConvexAuthProvider` with
  `api: { refreshSession, signOut }`) and the provider hooks
  (`useSignInWithGoogle`, `useSignInWithApple`, password helpers). Route protection is
  client-side (`Authenticated`/`Unauthenticated`); v2 ships no Next.js middleware helper.
* Backend identity is always derived server-side (`getAuthUserId` or
  `ctx.auth.getUserIdentity().tokenIdentifier`); never accept a caller-supplied owner ID.

A separate identity plane for a client application's customers remains allowed under the same
conditions as ADR 0002 (independently owned user population, legal boundary, or identity
adapter) — implemented with Convex Auth v2, not Clerk.

## Consequences [#consequences]

* Clerk is no longer the platform authentication provider. Clerk dependencies are removed from
  each app as it migrates; apps with existing Clerk user populations (Construct, Observatory)
  need a per-app user-mapping or re-login plan before cutover.
* ~~OAuth provider approval is platform-level: the approved catalog (Google, GitHub; Apple per-app)
  is defined in `docs/exo-auth-contract.md` in Construct and mounted uniformly by every Exo app.
  A provider added for one app is added to the catalog and swept into every app.~~ &#x2A;*Superseded by [ADR 0004](0004-exo-id-central-identity-provider.md):** the
  provider catalog (Google, GitHub; Apple when added) is mounted once, at Exo. There is no
  per-app Apple or any other per-app provider.
* ~~OAuth client registration (Google Cloud Console, GitHub, Apple Developer) is per-provider platform
  setup, recorded in the app's env contract; Convex Auth does not broker those registrations.~~ &#x2A;*Superseded by [ADR 0004](0004-exo-id-central-identity-provider.md):**
  Exo holds the only OAuth client registrations; apps never touch Google Cloud Console, GitHub
  OAuth settings, or Apple Developer.
* v2 is pre-GA: pin the alpha version per app, track the `reboot` branch, and budget a
  version-bump pass before Q4. Upgrades are contained to `convex/auth.ts`,
  `convex/auth.config.ts`, `convex/convex.config.ts`, and the client provider.
* The ADR 0002 migration gate is retired with it; identity verification moves to per-app
  auth/isolation tests plus the Convex Auth component contract tests.
* Payload local authentication in EXO itself remains the admin fallback until the Convex-native
  frontend ships; this ADR governs the app identity plane, not Payload's internal admin login.
