Skip to content

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.

Better Auth compared with Supabase Auth on pricing, fit, trade-offs, required companions, environment variables, dependencies, MCP servers and repo footprint.
From the manifestOption ABetter AuthOption BSupabase Auth
In one lineBetter 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.

PricingBetter 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 forBetter 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 manifestBetter Auth
  • 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.
Supabase Auth
  • Authorization lives in SQL policies, not in TypeScript. That is the whole point, and it is also the learning curve: you debug permissions with explain and set 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.
Required companionsAdded for you, with a reasonBetter Auth
  • An ORM battery
  • A database battery
  • An email battery
Supabase Auth
  • Supabase
Recommended alongsideSuggested, never added for youBetter Auth

Nothing suggested.

Supabase Auth
  • A file storage battery
Env vars you will manageEvery one documented in docs/onboard.mdBetter Auth

10 variables · 2 required

  • BETTER_AUTH_SECRET
  • BETTER_AUTH_URL
  • 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

0 variables

None.

Dependencies addedBetter Auth
  • better-auth ~1.7.5
  • server-only ^0.0.1
Supabase Auth
  • @supabase/ssr ^0.12.7
  • @supabase/supabase-js ^2.117.0
  • server-only ^0.0.1
  • supabase ^2.2.1 (dev)
MCP serversWritten into .mcp.jsonBetter Auth
  • better-auth: https://mcp.better-auth.com/mcp
Supabase Auth

None. No extra agent tools from this one.

Footprint in your repoBetter 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

.env.localbash13 lines
# 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

.env.localbash1 line
# 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)

Supabase Auth (7)

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 explain and set 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.