Files
clawmates/docs/clerk.md
T
Omar SobhandClaude Fable 5 b9fdec9173 Clerk deployment smoke: validated against a real instance, both halves
Last open item from the roadmap + post-1.0 list. Run against the live
Clerk instance closing-seasnail-39.clerk.accounts.dev.

- Backend (crates/cm-auth/tests/live_clerk.rs, CM_LIVE_CLERK=1): pulls
  REAL discovery + JWKS from the live instance, mints a REAL session JWT
  via Clerk's Backend API (create user -> open session -> session token),
  and runs it through AuthService::authenticate — verify + JIT provision
  (keyed on the real sub), duplicate-subject suppression, tamper
  rejection against the live JWKS. Decodes the instance domain from the
  publishable key; cleans up the test user after. PASSING
- Frontend: built with AUTH_MODE=clerk + real keys, next start serves
  Clerk's <SignIn /> at /login wired to the instance (instance domain +
  data-clerk attributes present in the HTML). Both halves confirmed
  end to end against production Clerk
- docs/clerk.md: documented the smoke procedure for both halves

166 Rust tests (+6 live, key-gated). Keys used via env only, never
stored — rotate them (they passed through chat).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 13:39:26 -05:00

97 lines
3.7 KiB
Markdown

# Using Clerk for authentication
Clawmates's cloud target can use [Clerk](https://clerk.com) as its identity
provider. A Clerk instance is an OIDC issuer, so the backend verifies Clerk
session JWTs directly against the instance JWKS — no extra service, no
session table writes for SSO users, and Clerk's dashboard takes over user
management, MFA, and organization invitations.
**Air-gapped installs cannot use Clerk** (it is a hosted service). The
`local` auth mode remains the appliance default; `clerk` is a cloud-only
alternative to generic `oidc`.
## 1. Clerk dashboard
1. Create an application; note the Frontend API URL
(`https://<slug>.clerk.accounts.dev`).
2. Enable **Organizations** — Clerk's `org:admin` / `org:member` roles map
to Clawmates `Owner` / `Member`.
3. Under **Sessions → Customize session token**, add the claims Clawmates
reads:
```json
{
"email": "{{user.primary_email_address}}",
"role": "{{org.role}}"
}
```
## 2. Backend configuration
```toml
[auth]
mode = "clerk"
issuer_url = "https://<slug>.clerk.accounts.dev"
```
Or via Helm: `--set auth.mode=clerk --set auth.issuerUrl=https://<slug>.clerk.accounts.dev`.
On boot the server pins the issuer, loads its JWKS (refreshing on key
rotation), and verifies every `Authorization: Bearer <session JWT>` with a
5-second clock-skew allowance. Users are JIT-provisioned on first sight,
keyed by the stable `sub` claim (`users.auth_subject`); an existing
local-auth user with the same email is linked rather than duplicated, and
the role tracks the Clerk org role on every request.
## 3. Frontend wiring (built in)
The frontend ships with the integration. Set three runtime env vars on
the frontend container — the SAME image serves local and Clerk
deployments, nothing is baked at build time:
```bash
AUTH_MODE=clerk
CLERK_PUBLISHABLE_KEY=pk_live_…
CLERK_SECRET_KEY=sk_live_…
```
What flips on: the root layout wraps the tree in `<ClerkProvider>`
(lazily imported — local mode never loads the SDK), `/login` renders
Clerk's `<SignIn />`, the middleware runs `clerkMiddleware()`, and every
server-side API call resolves its bearer through Clerk's `getToken()`
instead of the local session cookie (`src/lib/auth/bearer.ts` is the
single dispatch point). Clerk session JWTs live ~60s and refresh
transparently; the backend verifies each one against the instance JWKS.
## What is verified in CI
`crates/cm-auth/tests/jwt_auth.rs` and `crates/cm-api/tests/clerk_api.rs`
exercise the full verification path with real RSA keys against a live
local issuer publishing real discovery + JWKS documents: JIT provisioning
and role mapping, duplicate-subject suppression, expired tokens, tokens
signed by the wrong key, foreign issuers, and the end-to-end
`Authorization: Bearer` round trip into a protected endpoint.
## Deployment smoke (real instance)
`crates/cm-auth/tests/live_clerk.rs` validates the production path against
a **live Clerk instance**: it pulls real discovery + JWKS, uses Clerk's
Backend API to create a user, open a session, and mint a real session JWT,
then runs that token through `AuthService::authenticate` — proving
verification, JIT provisioning, duplicate-subject suppression, and
tamper rejection against Clerk's actual keys. It cleans up the user
afterward. Run it with your instance keys (key-gated, never in the default
suite):
```bash
CM_LIVE_CLERK=1 \
CLERK_SECRET_KEY=sk_test_… \
CLERK_PUBLISHABLE_KEY=pk_test_… \
cargo test -p cm-auth --test live_clerk
```
The frontend half is smoked by building with `AUTH_MODE=clerk` and the
same keys and confirming `/login` serves Clerk's `<SignIn />` wired to the
instance — `npx next start` then `curl /login` shows the instance domain
and `data-clerk` attributes.