oc · docs
docs / ochk.io · auth host

ochk.io · auth host API

The OrangeCheck family auth host. Issues Ed25519-signed oc_session cookies (Domain=.ochk.io) that every consumer subsite verifies locally via @orangecheck/auth-core + @orangecheck/auth-client — no DB on consumers, no per-request fetch to the auth host.

Live spec & interactive explorer. The canonical contracts are at https://ochk.io/api/openapi (OpenAPI 3.1 JSON, CORS-permissive, cached 5 min) and https://ochk.io/api-explorer (Swagger UI with "Try it out" enabled). Generate clients with openapi-generator-cli generate -i https://ochk.io/api/openapi -g <lang>.

Surface map

TagEndpointsPurpose
auth/api/auth/jwks, /me, /signin, /logout, /switch, /accountSession lifecycle. JWT verification key, current session, sign-in/out, multi-account switch, account edit.
challenge/api/challenge (GET + POST)BIP-322 signed-challenge primitive — issue + verify, the building block.
email-otp/api/auth/email-otp/start, /verifyEmail-OTP sign-in. Same session-cookie outcome as BIP-322.
oauth/api/auth/<provider>/start, /callback, /api/auth/providersGoogle / GitHub sign-in run on the auth host. Same session-cookie outcome; /providers lists the configured providers.
connect/api/connect/provision, /api/connect/appsOrangeCheck Connect — provision a did:oc for an integrator's own users. See Integration.
public/api/family-statsCross-subdomain family-wide stats. No auth.
forms/api/contactContact-form relay via Resend. Same-origin only.

Auth scheme

cookieAuth — the oc_session cookie issued by /api/auth/signin (or /api/auth/email-otp/verify). Cookie attributes:

  • Domain=.ochk.io (cross-subdomain)
  • HttpOnly (no JS access)
  • Secure (TLS only)
  • SameSite=Lax

Consumer sites verify locally — no fetch to the auth host on every request. The Ed25519 public JWK lives at /api/auth/jwks and /.well-known/jwks.json; consumers cache it as the OC_AUTH_PUBLIC_JWK env var at deploy time.

Sign-in primitives

Three paths, one outcome — each lands the same oc_session cookie, so consumer code never branches on how the user signed in.

BIP-322 (canonical):

  1. GET /api/challenge?addr=...&audience=...&purpose=login{ message, nonce, expiresAt }
  2. User signs message with their Bitcoin wallet via @orangecheck/wallet-adapter or directly.
  3. POST /api/auth/signin with { address, message, signature, nonce } → 200 + oc_session cookie set.

Email-OTP (lower-friction):

  1. POST /api/auth/email-otp/start with { email } → server emails 6-digit code
  2. POST /api/auth/email-otp/verify with { email, code } → 200 + oc_session cookie set.

OAuth providers (Google / GitHub):

  1. GET /api/auth/<provider>/start → redirect to the provider's consent screen.
  2. The provider redirects back to /api/auth/<provider>/callback; the auth host verifies the result and sets the oc_session cookie.
  3. GET /api/auth/providers lists which providers are configured — this is what <OcSignIn> reads to render its provider buttons.

A provider sign-in mints the same federation-custodied account that email-OTP does. For layering OrangeCheck onto an integrator's own OAuth instead, see OrangeCheck Connect.

Multi-account sign-in

Hold multiple OrangeCheck accounts in one browser and switch between them without re-authenticating — same pattern as Google or GitHub. Three endpoints touch this: POST /api/auth/signin?add=1 (and the email-OTP equivalent) brings a new account into the browser's roster; POST /api/auth/switch flips the active session; POST /api/auth/logout?scope=current signs out of just the current account while keeping the rest of the roster alive. Full narrative at Multi-account.

Live spec — embedded

openapi 3.1 · live spec·https://ochk.io/api/openapi·ochk.io · auth host
loading openapi reference…

The OAuth-provider (/api/auth/<provider>/*) and Connect (/api/connect/*) endpoints are newer than the embedded OpenAPI document — their contracts are documented under Integration and OrangeCheck Connect until the spec catches up.

See also