Better Auth vs Supabase Auth 2026: Honest Verdict

Better Auth vs Supabase Auth 2026: Honest Verdict

# supabase# betterauth# authentication# nextjs
Better Auth vs Supabase Auth 2026: Honest VerdictMahdi BEN RHOUMA

The choice is not features, it is where authorisation lives. If your rules run in Postgres RLS, Better Auth changes your architecture. Here is the call.

Most comparisons of these two list sign-in methods side by side, notice that
both support email, OAuth, magic links and passkeys, and conclude that it is a
matter of taste.

It is not a matter of taste. There is one decision underneath, and it is
architectural: where is authorisation enforced?

Supabase Auth exists to put an identity inside the database, so that Postgres
itself can decide what a request may read. Better Auth exists to put identity
inside your application, so that your TypeScript code can decide. Everything
else — the provider list, the plugin catalogue, the pricing — follows from that
and matters far less.

The mechanism you are actually choosing between

Supabase Auth: the database knows who you are

When a Supabase client makes a request, it carries a JWT signed with your
project's secret. PostgREST validates it, sets the Postgres role from the
token's role claim, and puts the whole claim set into a session variable.
auth.uid() is then a small SQL function that reads sub out of that variable.

That is the entire trick, and it is why a row level security policy can say:

create policy "read own documents"
on documents
for select
to authenticated
using ( (select auth.uid()) = user_id );
Enter fullscreen mode Exit fullscreen mode

The consequence is real: a developer who forgets a where user_id = ... in a
query does not leak data, because the database refuses the rows regardless. Your
authorisation rules live in one place, next to the data, and apply to every
client that ever connects — your web app, your mobile app, a script someone runs
at 2am.

The cost is that those rules are written in SQL, are awkward to unit test, and
fail in ways that are quiet by design. That is why I built the
RLS Playground — it runs a real Postgres engine
in the browser and executes your policies as anon, as two different signed-in
users and as service_role, side by side, so "does this policy actually isolate
tenants?" becomes a question you answer by looking rather than by reasoning.

Better Auth: your application knows who you are

Better Auth describes itself as a framework-agnostic authentication and
authorisation framework for TypeScript, with database management and migrations
handled for you and capabilities such as two-factor, organisations, rate
limiting and access control available in the core or as plugins.

Practically, it means users and sessions become ordinary tables in your
database. You can join them. You can migrate them with the rest of your schema.
You can read them in a test without mocking a network service. Your getSession()
is a database read in your own process, not a round trip to an identity
provider.

Authorisation then happens in your server code — in a service layer, a
middleware, or whatever pattern your team already uses. It is easy to test,
easy to express complex rules in, and completely dependent on every query going
through it. A raw query written in a hurry bypasses the whole thing.

The mistake to avoid: assuming they compose

The tempting architecture is Better Auth for identity, Supabase for the
database. It is a reasonable thing to want, and it works — but not by default,
and the failure mode is silent.

Two cases:

You connect to Postgres directly from your server, with Drizzle or Prisma
over a normal connection. That connection authenticates as a database user of
your choosing, not as authenticated, and no JWT claims are set. Your RLS
policies are simply not consulted in any meaningful way, and if the role is the
table owner or a superuser they are skipped entirely. Everything appears to
work — because every query returns everything.

You go through PostgREST (the supabase-js client). Then the request needs
a JWT signed with your project's secret and carrying sub and role, or the
API rejects it. Minting one from Better Auth is possible, but you are now
maintaining a token bridge and two sources of truth about session lifetime and
revocation.

If you choose this combination, decide deliberately which of the two you are
doing, and write it down. The version I would avoid is drifting into case one
while still believing RLS protects you. The
testing procedure here will
tell you within a minute whether your policies are being enforced at all.

Where each one is clearly right

Supabase Auth is the right call when:

  • You already run Supabase. Auth is provisioned, and storage, realtime and the auto-generated API all expect its JWTs. Replacing it means replacing their authorisation model too.
  • Authorisation is data-shaped: rows belong to users, users belong to organisations, and the rules are expressible as predicates. See RLS policy design patterns.
  • More than one client will reach the database, and you want the guarantee to hold for all of them rather than trusting each one.
  • You want the shortest path from zero to a working signed-in app — the complete Supabase auth guide for Next.js is roughly an afternoon.

Better Auth is the right call when:

  • Your database is your own Postgres — Neon, Hetzner, Scaleway, RDS, whatever — and you have no intention of adopting the rest of the Supabase platform.
  • Authorisation is behaviour-shaped rather than row-shaped: quotas, workflow states, plan limits, per-endpoint permissions. Expressing those in SQL policies is possible and unpleasant.
  • You want auth in your repository, reviewable in pull requests and testable in CI, rather than configured in a dashboard.
  • Cost predictability at scale matters. Users in your own tables have no monthly-active-user tier to cross.

The parts that do not decide it

A few axes get more attention than they deserve.

Provider coverage. Both do email and password, the mainstream OAuth
providers, magic links, OTP and passkeys. Check the specific niche provider you
need; otherwise this is a tie.

Organisations and RBAC. Better Auth ships organisation and access-control
capability as part of the framework. Supabase gives you tables and policies and
expects you to build the model — which is more work up front and completely
yours afterwards. See
multi-tenant SaaS architecture on Supabase
for what that build looks like.

Lock-in. Both store users in Postgres, so in either case you can export the
table. What is not portable is the authorisation layer: RLS policies assume
Supabase-minted JWTs, and application-layer rules assume your session object.
Migrating auth is cheap; migrating enforcement is not.

A note for EU teams

If you are handling personal data under the GDPR, the question is where the
identity records physically live.

With Better Auth they live in your database, so an EU-hosted Postgres keeps them
in the EU and adds no additional processor to your record of processing
activities. With Supabase Auth they live in your Supabase project — fine for EU
residency provided you pick an EU region when you create the project, since
the region cannot be changed later. Migrating a live project across regions is
a data migration, not a setting.

Either way, both give you the account deletion primitive you need for erasure
requests; with Better Auth it is a delete in your own schema, and with Supabase
it is an admin API call plus whatever cascades you defined on your own tables.

The verdict

Ask one question: if a developer on your team writes a query without a
where clause on the tenant column, what stops the leak?

If the answer needs to be "the database", you want Supabase Auth, and you should
invest the afternoon it takes to test your policies properly rather than
assuming them. If the answer can be "our service layer, which is reviewed and
tested", Better Auth gives you a cleaner, cheaper, more testable system that
lives entirely in your repository.

Both are good. They are good at different things, and the thing they differ on
is not one you can change later without a rewrite.

For the third option in this space — a managed provider that owns the identity
layer entirely — the trade-offs are laid out in
Supabase Auth vs Clerk,
and the wider field is surveyed in
Next.js auth in 2026.


Originally published at https://www.iloveblogs.blog