auth · side by side
Better Auth vs Supabase Auth for a Next.js app
Both fill the auth slot, so a generated repo carries one or the other, never both. Every line below is read out of the two manifests.
Short answer
Pick Better Auth if
Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path.
Pick Supabase Auth if
Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows.
Better Auth is tested with Neon. Supabase Auth isn't yet.
Side by side
Price, obligations, and the surface each one adds. No row is written by hand. This is manifest.yaml, rendered.
| From the manifest | Option ABetter Auth | Option BSupabase Auth |
|---|---|---|
| In one line | Better Auth Own your users table. Passwords, magic links, Google, GitHub and Microsoft, all in your database. | Supabase Auth Postgres-native auth where the database, not the API layer, is the last line of defence. |
| Pricing | Better Auth Free and open source (MIT). You pay only for your own Postgres and the sign-in emails you send. Google, GitHub and Microsoft sign-in are free. | Supabase Auth Free: 50,000 monthly active users. Pro: 100,000 included, then $0.00325 per MAU. Anonymous sign-ins are included. SAML SSO needs Pro: 50 SSO users included, then $0.015 each. |
| Best for | Better Auth Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path. | Supabase Auth Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows. |
| Trade-offsVerbatim from the manifest | Better Auth
| Supabase Auth
|
| Required companionsAdded for you, with a reason | Better Auth
| Supabase Auth
|
| Recommended alongsideSuggested, never added for you | Better Auth Nothing suggested. | Supabase Auth
|
| Env vars you will manageEvery one documented in docs/onboard.md | Better Auth 10 variables · 2 required
| Supabase Auth 0 variables None. |
| Dependencies added | Better Auth
| Supabase Auth
|
| MCP serversWritten into .mcp.json | Better Auth
| Supabase Auth None. No extra agent tools from this one. |
| Footprint in your repo | Better Auth 62 files, plus 5 injections into shared stack files | Supabase Auth 60 files, plus 8 injections into shared stack files |
What changes in your repo
The paths each battery contributes, diffed. A path in the third list is written by both, so swapping rewrites that file rather than adding one.
Only with Better Auth (40)
scripts/1 file
auth/1 file
- make-admin.ts
src/32 files
app/7 files
(better-auth)/6 files
banned/1 file
- page.tsx
forgot-password/1 file
- page.tsx
reset-password/1 file
- page.tsx
sign-in/1 file
- page.tsx
sign-up/1 file
- page.tsx
- layout.tsx
api/1 file
auth/1 file
[...all]/1 file
- route.ts
components/10 files
auth/10 files
settings/4 files
- connected-accounts-card.tsx
- password-card.tsx
- profile-card.tsx
- reauthenticate-alert.tsx
- auth-setup-notice.tsx
- check-inbox.tsx
- focus-first-error.ts
- header-actions.tsx
- magic-link-form.tsx
- session-provider.tsx
lib/15 files
auth/15 files
- action-limit.test.ts
- action-limit.ts
- auth.ts
- endpoint-guard.test.ts
- endpoint-guard.ts
- guards.test.ts
- list-sessions.test.ts
- list-sessions.ts
- policy.ts
- providers.test.ts
- roles.ts
- secret.ts
- setup.ts
- user-agent.test.ts
- user-agent.ts
tests/1 file
e2e/1 file
- outbox.ts
variants/6 files
orm-drizzle/3 files
slots/1 file
- db-schema.ts
src/2 files
lib/2 files
auth/2 files
- adapter.ts
- schema.ts
orm-prisma/3 files
slots/1 file
- prisma-models.prisma
src/2 files
lib/2 files
auth/2 files
- adapter.ts
- schema.ts
Only with Supabase Auth (38)
src/34 files
app/9 files
(supabase-auth)/6 files
forgot-password/1 file
- page.tsx
no-access/1 file
- page.tsx
reset-password/1 file
- page.tsx
sign-in/1 file
- page.tsx
sign-up/1 file
- page.tsx
- layout.tsx
auth/3 files
callback/1 file
- route.ts
confirm/1 file
- route.ts
sign-out/1 file
- route.ts
components/7 files
auth/7 files
settings/4 files
- connected-accounts.tsx
- form-feedback.tsx
- password-form.tsx
- profile-form.tsx
- check-email.tsx
- header-auth-actions.tsx
- supabase-session-listener.tsx
lib/18 files
auth/18 files
- admin.ts
- constants.ts
- database.types.ts
- env.ts
- flow.ts
- form-state.ts
- identity.test.ts
- impersonation.ts
- origin.ts
- profile.ts
- provider-settings.test.ts
- provider-settings.ts
- proxy.test.ts
- proxy.ts
- redirect.test.ts
- redirect.ts
- rls.ts
- server.ts
supabase/2 files
migrations/2 files
- 0100_supabase_auth_profiles.sql
- 0101_supabase_auth_profile_sync.sql
variants/2 files
orm-prisma/2 files
slots/1 file
- prisma-external-tables.ts
supabase/1 file
migrations/1 file
- 0102_prisma_profiles_without_cross_schema_fk.sql
Same path, different implementation (22)
scripts/1 file
auth/1 file
- seed.ts
src/18 files
components/12 files
auth/12 files
settings/2 files
- email-card.tsx
- sessions-card.tsx
- account-settings.tsx
- auth-card.tsx
- forgot-password-form.tsx
- oauth-buttons.tsx
- password-input.tsx
- provider-icons.tsx
- reset-password-form.tsx
- sign-in-form.tsx
- sign-out-button.tsx
- sign-up-form.tsx
lib/6 files
auth/6 files
- actions.ts
- client.ts
- errors.ts
- providers.ts
- schemas.ts
- session.ts
tests/3 files
e2e/3 files
- auth.setup.ts
- auth.spec.ts
- auth.ts
Shared stack files Better Auth injects into
- bare-route-groups
- env-required
- header-actions
- proxy-handlers
- verify-checks
Shared stack files Supabase Auth injects into
- bare-route-groups
- env-required
- header-actions
- legal-processors
- middleware-matchers
- providers
- proxy-handlers
- verify-checks
Better Auth in your .env.local
# required
BETTER_AUTH_SECRET=replace-me
BETTER_AUTH_URL=http://localhost:3000
# optional
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_TENANT_ID=
VERCEL_URL=
Supabase Auth in your .env.local
# Supabase Auth adds no environment variables.
What changes for your agents
Each battery ships rules, skills, subagents and hooks that an agent loads before it touches the code that battery owns. Picking one is also picking how your agents behave in src/lib/auth/**.
Better Auth
2
Skills
2
Rules
10
Solution docs
1
MCP servers
Rules (2)
The auth server boundary and sign-in methods
src/lib/auth/** · src/app/api/auth/** · src/app/(better-auth)/** · src/components/auth/**
Roles are decided on the server, every time
src/app/** · src/components/** · src/lib/auth/**
Skills (2)
/add-oauth-provider
Turn on Google, GitHub or Microsoft sign-in (env keys only), or add another OAuth provider to the list in src/lib/auth/providers.ts.
/protect-route
Put an authentication or role check on a page, a route handler, a server action or a whole route group, at the right layer, without a redirect loop.
Subagents and hooks
None of its own. The foundation agents and guard hooks still ship.
Supabase Auth
3
Skills
4
Rules
7
Solution docs
Rules (4)
Every table needs row-level security and at least one policy
supabase/** · src/lib/auth/**
getUser() is the trust boundary, getSession() is not
src/lib/auth/** · src/app/** · src/components/**
The service-role key is a root password
src/lib/auth/** · src/app/** · supabase/**
Sign-in flows keep return paths safe and reveal nothing about accounts
src/components/auth/** · src/lib/auth/** · src/app/(supabase-auth)/** · src/app/auth/**
Skills (3)
/add-auth-rls-policy
Add row-level security policies to a Supabase table (owner-scoped, tenant-scoped or admin) with the indexes and the tests that prove they work.
/add-oauth-provider
Switch on Google, GitHub or Microsoft sign-in for Supabase Auth, or add a new provider (Apple, Discord, LinkedIn) with its button, icon, redirect URLs and profile mapping.
/add-profile-field
Add a field people edit on /settings/profile (a bio, a time zone, a company) with Supabase Auth, stored in public.profiles behind a column grant, validated with zod and saved by a server action.
Subagents and hooks
None of its own. The foundation agents and guard hooks still ship.
What each one already knows
Solution docs land in docs/solutions/ in your repo and are published here, so you can read the failure modes before you commit.
Better Auth (10)
- Account linking and email verification without account takeoversWhen "Continue with Google" joins an existing password account, when it refuses, and why an unverified email address or a trusted provider list can hand one person's account to another.docs/solutions/better-auth/account-linking-and-email-verification.md
- Better Auth on the edge: why your session check fails in middlewareEdge runtimes have no TCP sockets and no Node crypto, so a session lookup that works in a page throws in the proxy. Read the cookie there and verify in the render.docs/solutions/better-auth/better-auth-on-the-edge.md
- CSRF, SameSite and the cookie flags that make a session safeWhat each session cookie flag actually defends against, why trustedOrigins is your CSRF check, and the three configuration changes that quietly disable both.docs/solutions/better-auth/csrf-and-cookie-flags.md
- Guard Better Auth's endpoints, not just your settings formsEvery /api/auth endpoint is a public URL. One hook refuses account changes from an impersonation session and asks for a recent sign-in before a password or provider is added.docs/solutions/better-auth/guarding-the-auth-api-itself.md
- The magic-link token: single use, ten minutes, and the scanner that clicks it firstHow long the credential lives, why a corporate mail scanner burns it before the human arrives, and why the rate limiter has to be backed by your database rather than by process memory.docs/solutions/better-auth/magic-link-tokens-and-scanners.md
- Moving an existing user table onto Better Auth without logging everyone outMap your columns to the four required tables, backfill ids and accounts, and let people migrate themselves on next sign-in instead of forcing a password reset.docs/solutions/better-auth/migrating-an-existing-user-table.md
- oauth-callback-url-mismatchesdocs/solutions/better-auth/oauth-callback-url-mismatches.md
- Password reset tokens that cannot be replayed, guessed or leakedOne hour, single use, answered the same way for every address, and kept out of logs, Referer headers and search results. What Better Auth does for you and the four things it cannot.docs/solutions/better-auth/password-reset-tokens.md
- Modelling roles you will not regret when the admin panel growsA role column, a ranked vocabulary in one file, and permission checks that name the action, not a boolean isAdmin scattered across forty components.docs/solutions/better-auth/role-modelling-for-the-admin-panel.md
- Session invalidation, or why everyone got logged out on deployA rotated secret, a changed cookie name or a wiped database invalidates every session at once. Here is what invalidates what, and how to revoke one user on purpose.docs/solutions/better-auth/session-invalidation-and-logged-out-on-deploy.md
Supabase Auth (7)
- Logged out after an hour: Supabase cookie refresh in the App RouterAccess tokens expire hourly and Server Components cannot write cookies, so the refresh has to happen in the proxy and be returned on the same response object.docs/solutions/supabase-auth/cookie-refresh-in-the-app-router.md
- getSession() vs getUser(): the Supabase trust trapgetSession() decodes a cookie the browser controls; getUser() verifies it with the auth server. On the server, only one of them is a security check.docs/solutions/supabase-auth/getsession-vs-getuser.md
- Admin impersonation on Supabase Auth, bound to one sessionSupabase Auth has no "view as user". Build it from a server-side magic link plus an app_metadata marker tied to the new session id, so only that session is flagged and nobody can forge it.docs/solutions/supabase-auth/impersonation-bound-to-one-session.md
- Migrating an app that only ever used the anon keyTables with RLS off are public. Turn it on table by table behind a feature switch, write the policies, and fix the queries the policies break, in that order.docs/solutions/supabase-auth/migrating-from-anon-key-only-access.md
- Show only the OAuth buttons your Supabase project has switched onRead the public /auth/v1/settings endpoint on the server, cache it, and fail closed, so a sign-in page never shows a Google button that ends on an error page.docs/solutions/supabase-auth/oauth-buttons-from-auth-settings.md
- RLS policy patterns for multi-tenant rowsOwner-scoped, org-scoped and role-scoped policies, the with-check clause people forget, and the indexes that stop a policy from turning every read into a scan.docs/solutions/supabase-auth/rls-patterns-for-multi-tenant-rows.md
- The blast radius of a leaked Supabase service-role keyThe key bypasses every policy for every table. Here is how it leaks, what an attacker gets, how to contain it, and how to make the leak impossible.docs/solutions/supabase-auth/service-role-key-blast-radius.md
Which one to pick
From meta.bestFor and meta.tradeoffs. If a claim is not in the manifest, it is not on this page.
Pick Better Auth when
Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path.
And accept that(6)
- You own the security surface. Nobody rotates your signing secret, patches your session logic or answers a pen-test questionnaire for you.
- No hosted UI. Sign-in and sign-up screens are yours to build and style, which is why this battery ships real ones instead of a redirect.
- Email deliverability is your problem. A reset or magic link that lands in spam is an outage for that person.
- Each OAuth provider is an app you register and keep alive yourself: callback URLs per environment, and a Microsoft client secret that expires.
- Enterprise features other vendors sell as a plan tier (SAML, SCIM, audit log) are plugins or your own code here.
- Upgrades are yours to run. New Better Auth minors sometimes add columns, so regenerating the schema is part of every upgrade.
Pick Supabase Auth when
Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows.
And accept that(5)
- Authorization lives in SQL policies, not in TypeScript. That is the whole point, and it is also the learning curve: you debug permissions with
explainandset role, not a debugger. - Auth is coupled to the Supabase project. Moving the database off Supabase means moving users, JWT signing and every policy at the same time.
- Cookie-based sessions in the App Router need a proxy refresh. Skip it and users get logged out after an hour with no error anywhere.
- Roles live in
app_metadata, which only the service-role key can write. Good for security, awkward for self-service role changes. - The service-role key bypasses every policy. One import of it into a client component and the whole database is public.
Questions people actually ask
- Should I choose Better Auth or Supabase Auth?
- Better Auth is best for teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path. Supabase Auth is best for teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows. Both fill the auth slot, so a generated repo carries one or the other, never both.
- How much do Better Auth and Supabase Auth cost?
- Better Auth: Free and open source (MIT). You pay only for your own Postgres and the sign-in emails you send. Google, GitHub and Microsoft sign-in are free. Supabase Auth: Free: 50,000 monthly active users. Pro: 100,000 included, then $0.00325 per MAU. Anonymous sign-ins are included. SAML SSO needs Pro: 50 SSO users included, then $0.015 each.
- What changes in my repo if I switch from Better Auth to Supabase Auth?
- Better Auth writes 62 files, 10 environment variables and 2 dependencies, and installs 2 path-scoped rules, 2 skills and 10 solution docs. Supabase Auth writes 60 files, 0 environment variables and 4 dependencies, and installs 4 path-scoped rules, 3 skills and 7 solution docs.
Decide once, then build the repo that already knows the decision.
Either way you get that choice’s rules, skills and solution docs installed, plus the guard hooks, an onboarding doc for exactly these env vars, and the Compound Engineering loop. Free and MIT.