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
-
Create an application; note the Frontend API URL (
https://<slug>.clerk.accounts.dev). -
Enable Organizations — Clerk's
org:admin/org:memberroles map to ClawmatesOwner/Member. -
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.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:
- Clerk Dashboard → User & Authentication → Social Connections.
- Toggle on Google and GitHub (Clerk's shared dev credentials work immediately for testing; add your own OAuth client IDs/secrets for prod).
- 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
.p8private 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.