Platform services
Exo Key
Exo is the only OAuth client and identity provider; Apps are relying parties that receive app-scoped RS256 tokens.
Sources: ADR 0004 and the Exo Key integration guide. The guide is the integration reference; this page summarizes it.
Naming: Exo Key was called Exo ID until 2026-09-29. Technical identifiers, including the
/exo-idendpoint paths, theexo-idADR filename and the code, keep the old name until the code rename lands.
Exo is the only OAuth client registered with Google, GitHub, and (later) Apple. An App never
mounts Convex Auth or OAuth provider components, never holds AUTH_PRIVATE_KEY, AUTH_JWKS, or
provider client secrets, and never touches a provider console. Onboarding an App means adding it
to convex/lib/exoIdApps.ts, and that is the whole provider-side setup.
Endpoints (staging)
| Purpose | URL |
|---|---|
Issuer (iss) | https://frugal-eel-930.convex.site/exo-id |
| Authorize (browser) | https://staging.exo.now/id/authorize |
| Token | POST https://frugal-eel-930.convex.site/exo-id/v1/token |
| Revoke | POST https://frugal-eel-930.convex.site/exo-id/v1/revoke |
| JWKS | https://frugal-eel-930.convex.site/exo-id/.well-known/jwks.json |
| Discovery | https://frugal-eel-930.convex.site/exo-id/.well-known/openid-configuration |
Read the endpoints from the discovery document instead of hard-coding them. Production endpoints: TBD (not recorded in the guide).
Flow: authorization code + PKCE (S256)
The App generates a code_verifier, its S256 code_challenge, and an opaque state.
It redirects to /id/authorize?app=…&redirect_uri=…&state=…&code_challenge=…&code_challenge_method=S256.
Exo validates the app and redirect_uri pair server-side. An invalid pair renders an error page
and never redirects.
After sign-in, Exo mints a one-time code (valid for 60 seconds, single use, stored only as a
SHA-256 hash) and redirects back with code and state.
The App checks state and exchanges the code at the token endpoint. It refreshes before
expires_in; refresh tokens rotate. Replaying the previous token within 30 seconds returns the
same new token; any other reuse of a rotated token signs the whole session out.
Sign-out posts the refresh token to the revoke endpoint, which always returns 200.
Tokens
- Access token: RS256 JWT, 900 seconds. Claims:
iss,aud(the App name),sub(the Exo user id, stable across Apps),email,email_verified,name,iat, andexp. - Refresh token: opaque, 30 days, rotating.
- Errors: HTTP 400 with
invalid_request,invalid_grant, orunsupported_grant_type. Any redemption attempt consumes the code. Too many token requests return HTTP 429 withRetry-After.
Relying Apps verify tokens in their own Convex backend with a customJwt provider that points at
the Exo Key issuer and JWKS. ctx.auth.getUserIdentity() then returns the Exo user id, email, and
name.
Registered Apps
| App | Redirect URIs (summary) |
|---|---|
observatory | staging subpath, observatory.bio |
construct | staging.construct.page, construct.page (server-side cookie session) |
rampart | staging subpath, rampart.fit |
dropship | staging subpath, dropship.wtf |
universe | staging subpath, native universe://auth/callback |
Redirect URIs match exactly, with no prefix, wildcard, query, or trailing-slash tolerance. Origins control CORS on the token and revoke endpoints.
Boundaries
- Authorization stays in the App (v1). Exo Key proves who the person is. Allowlists, roles, and tenant grants are enforced by the App on every request.
- Separate signing keys. Exo Key signs with
EXO_ID_PRIVATE_KEYandEXO_ID_JWKS, independent of Exo's own session keys. Rotation publishes the new key alongside the old one, waits at least the JWKS cache time (5 minutes) plus the access-token lifetime, then drops the old key. - Hard dependency. Exo's Convex deployment is on the sign-in path for the whole portfolio. Its availability and key rotation are platform responsibilities.
Cost notes
No license fee (Convex Auth is open source). Marginal Convex usage: TBD. The decision to leave Clerk cited its per-MAU pricing (ADR 0003).
Status
Live on staging for the five registered Apps. Clerk retirement is in progress App by App (roadmap: convex-auth). Centralized grants are future work.