Skip to content

Replaced built-in auth with Better Auth#620

Open
ivoilic wants to merge 1 commit into
SonicJs-Org:mainfrom
ivoilic:feature/better-auth
Open

Replaced built-in auth with Better Auth#620
ivoilic wants to merge 1 commit into
SonicJs-Org:mainfrom
ivoilic:feature/better-auth

Conversation

@ivoilic
Copy link
Copy Markdown

@ivoilic ivoilic commented Jan 31, 2026

Description

Replaces custom JWT auth with Better Auth. Rather than maintain auth on top of Sonicjs itself this outsources that work to Better Auth and allows end users to setup a larger number of secure auth methods. This is a first pass for consideration and certainly needs more work. Sign-in/sign-up at /auth/sign-in/email and /auth/sign-up/email. Session in HTTP-only cookie. OTP Login and Magic Link plugins removed but magic link/email OTP can no be added via auth.extendBetterAuth. RBAC and registration gating kept via Better Auth hooks.

Env: BETTER_AUTH_SECRET, BETTER_AUTH_URL

Changes

  • Core: New auth/config.ts (Better Auth + Drizzle, hooks). App mounts handler at /auth/*, session middleware sets c.set('user'). Auth middleware drops JWT/KV.requireAuth/requireRole use session. Login/register forms POST to Better Auth. Migrations 032 (Better Auth tables, users.name), 033 (drop otp/magic_link tables).
  • Removed: OTP Login plugin, Magic Link Auth plugin. Related admin UI and E2E specs (02c, 02d, 44).
  • Tests: Auth middleware and E2E 02/02b updated for session. Test-helpers use Better Auth login.
  • Docs: authentication, deployment, api-reference, routing-middleware, getting-started, architecture, FORMS_*, www auth/plugins/security/troubleshooting, docs/ai, type-check-failures — all updated for Better Auth.

Testing

  • npm test passes (40 files, 1174 tests)
  • npm run e2e or npm run e2e:smoke — run locally to confirm

Checklist

  • Code follows project conventions (lint passes, warnings only)
  • Tests added/updated and passing
  • Type checking passes
  • Documentation updated

@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented Apr 1, 2026

Hey @ivoilic — thanks for putting this together. Replacing the built-in auth with Better Auth is a solid architectural direction, and we appreciate the effort here. Removing ~6K lines of custom auth code in favor of a well-maintained library with OAuth providers, session management, and 2FA out of the box is exactly the kind of modernization SonicJS needs.

We've reviewed the PR and here's our assessment:

What we like:

  • Clean separation of concerns — Better Auth handles auth, SonicJS handles CMS
  • Significant code reduction (~6,100 lines removed, ~1,600 added)
  • OAuth provider support out of the box
  • Well-structured integration approach

Current blockers:

  • The PR is ~60 days stale and would need a rebase on latest main — we've made significant security changes since January (PBKDF2 hashing, CSRF protection, rate limiting, CORS, security headers, RBAC enforcement)
  • Migration path for existing users with current password hashes needs to be defined
  • E2E test coverage needed for the new auth flow
  • This is a breaking change that would need a major version bump

Our plan:
We're likely going to move in this direction in the near term. We're currently finishing up a security hardening cycle (v2.8.3–v2.9.x), and once that's stable, Better Auth integration makes a lot of sense as the foundation for v3.0.

Would you be interested in rebasing this on latest main when we're ready to kick off that work? We'd love to collaborate on making this happen. 🙏

@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented Apr 1, 2026

After further discussion, we've decided to take a different approach — instead of replacing the built-in auth entirely, we're going to build OAuth/social login as a plugin on top of the existing (now hardened) auth system. See issue #737 for the full plan.

Your PR was genuinely helpful as inspiration for the provider abstraction patterns. We'll be referencing it as we build the OAuth plugin.

We're closing this PR, but want to sincerely thank you @ivoilic for the work and the push toward better auth in SonicJS. If you're interested in contributing to the OAuth providers plugin (#737), we'd love to have you involved! 🙏

@lane711 lane711 closed this Apr 1, 2026
@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented Apr 1, 2026

Research: Better Auth + Cloudflare Workers Compatibility

For future reference, here's what we found when evaluating Better Auth for SonicJS's Cloudflare Workers runtime:

🚨 Critical: createRequire crash (Issues #6665, #6690)

Better Auth fails to load in Cloudflare Workers — crashes at module initialization with a createRequire(import.meta.url) error. Reported Dec 2025, still unresolved. No reliable workaround exists. This alone is a showstopper for SonicJS.

⚠️ 33-second hangs + 503s

A developer documented 33-second request hangs, mysterious 503s, and session dropouts when running Better Auth on Workers + D1. Root cause: Better Auth's singleton instance lifecycle doesn't fit Workers' stateless model. Dual auth instances create SQLite WAL lock contention in D1. Required completely rethinking auth instance creation (one per request, not singleton).

⚠️ D1 adapter issues (Issues #3552, #4732, #7487)

  • D1 adapter setup is problematic — multiple reports of it not working properly
  • Transactions defaulting to true in v1.3.10+ breaks D1 (D1 doesn't support nested transactions)
  • Kysely adapter requires specific undocumented wrapping

⚠️ Third-party wrapper required

The community had to create a separate better-auth-cloudflare package to make integration workable. Hono's own docs only recently added a CF Workers example.

Our decision

We're keeping SonicJS's built-in auth (hardened in v2.8.3–v2.9.x) and building OAuth/social login as a plugin instead (#737). The built-in auth is purpose-built for Cloudflare Workers + D1 and doesn't have these compatibility issues.

If Better Auth resolves these CF Workers issues in the future, we can revisit.

@lane711 lane711 reopened this May 29, 2026
@lane711 lane711 self-requested a review as a code owner May 29, 2026 01:38
@lane711
Copy link
Copy Markdown
Collaborator

lane711 commented May 29, 2026

Reopening: upstream blockers resolved, tentative v3 direction

Reopening this PR with updated context. @ivoilic — apologies for the long delay. Since this PR was closed on April 1, we've taken another look at the Better Auth + Cloudflare Workers landscape, and the picture has materially changed. The blockers cited at close time were actually already fixed when we wrote that comment.

Upstream blocker status (re-checked)

Issue cited at close Status today
better-auth #6665createRequire crash on Workers CLOSED Jan 3, 2026 (fix in 1.4.7-beta.2, Dec 10, 2025). Confirmed by multiple users in-thread.
better-auth #6690createRequire(import.meta.url) fails at module init CLOSED Jan 3, 2026 (same fix)
better-auth #4732 — Kysely transaction: true default breaks D1 CLOSED Sep 17, 2025
better-auth #3552 — D1 setup difficulties CLOSED Jul 23, 2025
better-auth #7487 Doesn't exist (likely a typo in the original comment)

In short: all four cited showstoppers were closed months before we cited them. That's on us — the closing comment should have held up better against re-verification.

Currently open Workers-adjacent issues

  • #7657 — Cross-origin auth regression in 1.4.x with Hono + CF Workers + Next.js proxy. Only affects setups where API and web app are on different domains with proxy rewrites. SonicJS serves admin + API from the same Worker, so this shouldn't bite us.
  • #8655 — Drizzle adapter double-stringify on SQLite. Real bug to watch since we're D1 + Drizzle. Needs verification on JSON-typed field round-tripping before commit.
  • #7440additionalFields: string[] returns stringified array. Adapter marshaling quirk, workaroundable.

No open Workers- or D1-blocking issues beyond these. The zpg6/better-auth-cloudflare shim (534⭐, last release v0.3.0 on April 5, 2026, active maintenance) handles per-request instance lifecycle, D1, Hyperdrive, KV, R2, and migrations. That covers the singleton/WAL-lock concerns from our earlier research note.

Better Auth itself is on v1.6.11 (May 12, 2026) with v1.7 in beta — shipping roughly weekly.

Why we're reconsidering: BetterAuth vs current SonicJS auth

Issues like #803 (custom profile fields stripped from /auth/me), #783 (role-based UI gating not enforced), and #382 (no SAML/SSO) are all symptoms of the same root cause: our current auth is role-name string checks ('admin' | 'editor' | 'author' | 'viewer') with no permission abstraction, no MFA, no SSO, no session revocation, no client SDK. Building all of that ourselves competes for time with CMS work.

Feature matrix

Core auth

Feature Better Auth SonicJS today
Email + password
Email verification / password reset
OTP / magic link ✅ plugin
Session management ✅ DB-backed, multi-device, revocable ⚠️ Stateless JWT (lives until expiry)
Account linking
Multi-session per user

Social / Enterprise SSO

Feature Better Auth SonicJS today
OAuth (Google, GitHub, etc.) ✅ 20+ providers built-in ⚠️ Planned (#737)
Generic OIDC
SAML ✅ plugin ❌ (#382)
Act as OIDC provider ✅ plugin

MFA

Feature Better Auth SonicJS today
TOTP (authenticator apps) ✅ plugin
Backup codes
Passkeys / WebAuthn ✅ plugin

Authorization / RBAC

Feature Better Auth SonicJS today
Roles ✅ multiple per user, configurable ⚠️ single string column, 4 hardcoded values
Verb-based permissions (resource × action) createAccessControl
Custom resources & verbs ✅ typed via as const
hasPermission API ✅ server + client
Per-tenant / per-org roles ✅ organization plugin
Sidebar/UI gating by role (you wire it) ⚠️ broken (#783)

Multi-tenancy

Feature Better Auth SonicJS today
Organizations / workspaces ✅ plugin
Org invitations
Teams within orgs

API & tokens

Feature Better Auth SonicJS today
JWT ✅ plugin ✅ default
API keys / PATs ✅ plugin
Token scopes / permissions ✅ verb-based ⚠️ JSON column, no enforcement engine
Token rotation / refresh ⚠️ JWT expiry only

Admin

Feature Better Auth SonicJS today
Admin plugin (impersonate, ban, set-role)
Rate limiting on auth routes ✅ built-in ⚠️ partial

DX

Feature Better Auth SonicJS today
Type safety end-to-end ✅ inferred ⚠️ partial
Client SDK ✅ official, framework adapters ❌ (#585)
Custom user fields surfaced in auth responses additionalFields ⚠️ defineUserProfile exists but stripped from /auth/me (#803)

Compliance

Feature Better Auth SonicJS today
Session revocation ✅ instant (DB-backed)
Audit logs ⚠️ available ⚠️ partial
GDPR data export / delete ⚠️ build it ❌ (#383)

Tentative plan: target v3.0

We're going to tentatively plan a Better Auth migration as part of v3.0. The reasoning:

  1. The upstream CF Workers blockers are gone.
  2. The better-auth-cloudflare shim handles the remaining Workers/D1 lifecycle nuances.
  3. Migrating would close a cluster of currently-open issues in one shot (User scope is not working as expected #783, /auth/me and /auth/otp/verify omit custom user profile fields (plan, etc.) #803, Enterprise SSO (SAML/OIDC) #382, Feature: Add JavaScript/TypeScript Client SDK for Frontend Frameworks #585, parts of GDPR compliance implementation #383/SOC 2 compliance preparation #384, the OAuth plugin work in Feature: OAuth/Social Login Providers Plugin #737 becomes free).
  4. We avoid building real RBAC, MFA, organizations, and a client SDK ourselves — those are months of work each.
  5. v3 is the right release vehicle since this is a breaking change.

Open questions we'll work through before merging:

  • Password rehash strategy. Existing users have PBKDF2 hashes (post v2.8.3 hardening). Plan: keep the old hash column, verify against it on next login, and re-hash via Better Auth on successful auth. No mass-rehash needed, no forced password resets.
  • Custom profile fields. Map defineUserProfile() to Better Auth's additionalFields config — this is the same primitive, so it should be a clean translation that also fixes /auth/me and /auth/otp/verify omit custom user profile fields (plan, etc.) #803.
  • Role translation. Map 'admin' | 'editor' | 'author' | 'viewer' to Better Auth roles using createAccessControl() with explicit resource × verb statements. Document the verbs as part of the v3 migration guide. This also gives us a path forward on User scope is not working as expected #783.
  • Session model shift. Moving from stateless JWT to DB-backed sessions changes the deployment story (every request now hits D1 for session lookup). Need to benchmark this on a realistic worker and decide if we want to layer a KV cache in front.
  • E2E coverage. This was rightly flagged at close. Will be a blocker for merge.
  • Pin a minor version. Better Auth has been willing to break things across minors (the 1.4.x cross-origin regression cluster). We'll pin a known-good version and upgrade deliberately.
  • Rebase on hardened main. All the v2.8.3+ security work (PBKDF2, CSRF, rate limiting, RBAC enforcement scaffolding) needs to merge through cleanly.

@ivoilic — would love your collaboration

Your original PR remains the right architectural direction. If you have bandwidth and interest in picking this back up against current main, we'd love to work with you on it as the foundation for v3. If not, totally understand — we'll reference your work as we plan internally either way. 🙏

Marking this as a v3 milestone target. Will leave the PR open while we scope and align.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants