Custom auth vs Supabase Auth: when to use which
Status: đźź© COMPLETE Last updated: 2026-06-21 Plain-English tagline: Supabase ships with a complete auth system (emails, OAuth, magic links, passkeys). Sometimes you should use it; sometimes a custom session table is simpler. This is when each fits.
What this decides
When you build a Supabase project, you choose where authentication lives:
- Supabase Auth — the built-in
auth.userssystem, with email/password, magic links, OAuth providers, passkeys, MFA, etc. Issues JWTs thatauth.uid()reads in RLS policies. - Custom auth — your own
userstable, your ownsessionstable, your own login flow. You validate credentials and issue session cookies yourself.
For background: Authentication vs authorization 🟩, Sessions and cookies 🟩, JWT 🟩, Supabase 🟩 🟦.
The short answer
Default to Supabase Auth for any project where users have email addresses and you want OAuth / passwordless / MFA.
Reach for custom auth when your situation doesn’t fit the email-centric model:
- Users don’t have email addresses (kids, internal codes, niche identifiers)
- You need a very specific login UX that Supabase Auth doesn’t support
- You’re building something simple enough that custom is genuinely less code
Bible Quest is the canonical custom-auth case: kids log in with auto-generated 8-character access codes (no email). Supabase Auth couldn’t model this cleanly. A custom sessions table did.
The factors that matter
- Do users have email addresses? Yes → Supabase Auth fits naturally. No → custom is easier.
- Do you need social login (Google, Apple, GitHub, etc.)? Yes → Supabase Auth (one config switch per provider). No → either works.
- Do you need MFA / passkeys? Yes → Supabase Auth (built-in). No → either works.
- How much auth surface do you need? Lots (signup, login, password reset, email verification, account deletion) → Supabase Auth saves you weeks. Minimal (one type of login) → custom may be ~50 lines.
- Are you using RLS with
auth.uid()? Yes → Supabase Auth makes this trivial. Custom auth means you do user-identity differently (server-side viaservice_role). - How comfortable are you with auth security? Auth bugs are catastrophic. Supabase Auth is battle-tested by thousands of apps. Custom auth means you’re the security expert.
When to pick SUPABASE AUTH
- Most consumer webapps — sign up with email, optionally social login, password reset via magic link. Standard pattern; Supabase Auth nails it.
- B2B SaaS — Supabase Auth supports SSO (SAML/OIDC) on paid tiers, which enterprise buyers expect.
- You want passwordless from day one — magic links and passkeys are first-class.
- You want OAuth — Google, GitHub, Apple, Discord, and ~20 more providers configured via dashboard toggle.
- You want MFA — TOTP + SMS supported out of the box.
- You need standard auth events to trigger backend work — Supabase emits webhooks on signup/login that you can hook into.
- You’re building anything where “users have email addresses” is reasonable — which is most apps.
Setup is roughly: enable the providers you want in the Supabase dashboard, install @supabase/auth-helpers-nextjs (or the newer @supabase/ssr), and use the signInWith* methods.
Reference: How-to: Add Supabase auth đźź©.
When to pick CUSTOM AUTH
- Users don’t have email addresses. Bible Quest’s kids get access codes. Internal employee tools may use SSO codes. A guest-checkout flow may use phone numbers without email.
- Your login UX is unusual. “Tap a card to log in,” “scan a QR code,” “your phone is your identity” — these don’t fit Supabase Auth’s model.
- You’re building something genuinely tiny where the full Supabase Auth surface is overkill. A “type the family password” landing page might be 30 lines of custom code.
- You need full control over the user record. Supabase Auth’s
auth.userstable has fixed columns; you can’t add fields (you add a separateprofilestable linked byuser_id). Custom auth can put everything in one table. - You want server-side sessions (cookies, no JWT in the client). Supabase Auth uses JWTs; custom auth can use server-side sessions directly.
Custom auth pattern (Bible Quest):
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
quest_name text NOT NULL,
access_code text UNIQUE NOT NULL,
role user_role NOT NULL DEFAULT 'kid'
);
CREATE TABLE sessions (
token uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL REFERENCES users(id),
expires_at timestamptz NOT NULL
);Then a lib/auth.ts with createSession, getCurrentUser, logout, requireAdmin — about 100 lines for everything Bible Quest needs.
What you give up with custom auth
Be honest about what you’re not getting:
- Password reset flows — you implement them yourself, or you don’t have them.
- Email verification — same.
- Audit logs of login events — Supabase Auth logs these; you’d build them.
- Rate limiting on login attempts — you’d add this yourself (or use a layer like Cloudflare).
- Session revocation / “log out everywhere” — possible with custom but requires explicit support.
auth.uid()in RLS — your queries can’t use it. You either route all queries through server code withservice_role, or you write a custom function that reads the session.
For Bible Quest, none of these mattered (kids don’t need password reset; the use case is internal). For most apps, they do.
The hybrid pattern (rare)
Some projects use Supabase Auth for some users and custom auth for others:
- A public app with Supabase Auth for end users + an admin tool with custom auth for staff
- A B2C app with Supabase Auth for consumers + a service-account model for partners
This adds complexity. Avoid unless the use case truly demands it.
What if I’ve already chosen?
“I started with custom auth and need to migrate to Supabase Auth”: you can backfill auth.users rows that point at your existing users records. Keep the existing IDs as id and add a user_metadata.legacy_user_id to link. Users get a “verify your email to claim your account” flow once.
“I started with Supabase Auth and want custom”: rarely worth it. The migration cost (re-issuing all auth, replacing all auth.uid() calls with custom session lookups, rewriting RLS) is large. Stay put unless you have a compelling reason.
“I used both and now they’re tangled”: consolidate. One auth system per app is much easier to reason about.
See also
- Authentication vs authorization 🟩 — the textbook
- Sessions and cookies đźź©
- JWT đźź©
- OAuth and social login đźź©
- Magic links and passwordless đźź©
- Supabase 🟩 🟦
- How-to: Add Supabase auth đźź©
- RLS vs server-side checks 🟩 — affected by the auth choice
- Secrets management đźź©