Clerk to BetterAuth Migration — SO1 Console
Date: 2026-03-16 Repos: so1-io/so1-console References: sparki-tools/web-app, smo1-io/meow-webWhat Changed
Migrated so1-console (Rover + BFF) from Clerk to BetterAuth, unifying the auth stack across the platform. Key changes:- Console: Replaced
@clerk/nextjswithbetter-auth. Custom login/signup pages at/auth/loginand/auth/signup. Cookie-based session (so1.session_token) instead of Clerk JWT. - BFF: Replaced
@clerk/backendJWT verification with sharedBFF_AUTH_SECRET+ trusted internal headers pattern. - E2E: Full auth cycle Playwright tests: signup, login, logout, session persistence, auth gate protection.
- CI: Separate
rover-e2e-authjob for auth cycle testing alongside existing smoke tests.
Pattern: Internal BFF Auth (Trusted Headers)
When the BFF sits behind a same-origin Next.js proxy, the proxy validates the BetterAuth session and forwards:- BFF only accepting the shared secret
- BFF only being accessible internally (not exposed to the internet)
Learnings
- BetterAuth is not JWT-based: Uses opaque session tokens + database lookup. Simpler than Clerk’s JWT/JWKS flow but means downstream services can’t decode tokens independently.
- Cookie prefix matters: The session cookie name is
{cookiePrefix}.session_token. Must match in middleware and auth config. - No ClerkProvider replacement needed: BetterAuth client hooks (
useSession,signIn, etc.) work without a provider wrapper — they use the base URL for API calls. - Build-time compatibility: BetterAuth config references
process.env.DATABASE_URLwhich is only available at runtime. Build succeeds with placeholder values in CI. - E2E auth is simpler: No two-step Clerk flow (identifier → password). BetterAuth login is a single form with email + password fields.