117 lines
4.7 KiB
Markdown
117 lines
4.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.
|
|
|
|
## 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**:
|
|
|
|
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.
|