Files
clawmates/docs/clerk.md
T

4.7 KiB

Using Clerk for authentication

Clawmates's cloud target can use Clerk 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:

    {
      "email": "{{user.primary_email_address}}",
      "role": "{{org.role}}"
    }
    

2. Backend configuration

[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:

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):

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.

Social OAuth (Google / GitHub / Apple)

The redesigned /login renders Clerk's <SignIn> themed to the dark design system (src/app/login/page.tsxclerkAppearance). 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 <SignIn> — the frontend needs no redeploy. Local dev (AUTH_MODE=local) keeps the email/password form and shows no social buttons.