exoDocs

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-id endpoint paths, the exo-id ADR 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)

PurposeURL
Issuer (iss)https://frugal-eel-930.convex.site/exo-id
Authorize (browser)https://staging.exo.now/id/authorize
TokenPOST https://frugal-eel-930.convex.site/exo-id/v1/token
RevokePOST https://frugal-eel-930.convex.site/exo-id/v1/revoke
JWKShttps://frugal-eel-930.convex.site/exo-id/.well-known/jwks.json
Discoveryhttps://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, and exp.
  • Refresh token: opaque, 30 days, rotating.
  • Errors: HTTP 400 with invalid_request, invalid_grant, or unsupported_grant_type. Any redemption attempt consumes the code. Too many token requests return HTTP 429 with Retry-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

AppRedirect URIs (summary)
observatorystaging subpath, observatory.bio
constructstaging.construct.page, construct.page (server-side cookie session)
rampartstaging subpath, rampart.fit
dropshipstaging subpath, dropship.wtf
universestaging 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_KEY and EXO_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.

Source: content/docs/platform/exo-key.mdx

On this page