# 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://.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://.clerk.accounts.dev" ``` Or via Helm: `--set auth.mode=clerk --set auth.issuerUrl=https://.clerk.accounts.dev`. On boot the server pins the issuer, loads its JWKS (refreshing on key rotation), and verifies every `Authorization: Bearer ` 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 `` (lazily imported — local mode never loads the SDK), `/login` renders Clerk's ``, 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 `` wired to the instance — `npx next start` then `curl /login` shows the instance domain and `data-clerk` attributes. ## Social OAuth (Google / GitHub / Apple) The redesigned `/login` renders Clerk's `` themed to the dark design system (`src/app/login/page.tsx` → `clerkAppearance`). The backend already validates any Clerk-issued JWT (generic OIDC), so **enabling social login is purely a Clerk-dashboard step — no code change**: 1. **Clerk Dashboard → User & Authentication → Social Connections.** 2. Toggle on **Google** and **GitHub** (Clerk's shared dev credentials work immediately for testing; add your own OAuth client IDs/secrets for prod). 3. **Apple** (uses the provided `AuthKey_*.p8`): enable Apple, then supply - **Services ID** (your Apple "Sign in with Apple" identifier), - **Apple Team ID**, - **Key ID** + the **`.p8` private key** contents, and add Clerk's callback URL to the Apple service's Return URLs. Once enabled, the "Continue with …" buttons appear automatically inside the themed `` — the frontend needs no redeploy. Local dev (`AUTH_MODE=local`) keeps the email/password form and shows no social buttons.