exoDocs
Decisions (ADRs)

Decisions and roadmap

ADR 0003: Convex Auth v2 as the shared identity plane

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

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. Superseded by ADR 0004: 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

  • 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. Superseded by ADR 0004: 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. Superseded by ADR 0004: 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.

Source: docs/adr/0003-convex-auth-v2-identity-plane.md

On this page