Every verified stack secured with Supabase Auth.
Supabase Auth: hosted identity (GoTrue) via the @supabase/ssr cookie client; email+password + OAuth.
240 verified stacks
Showing 12 across 12 app types. Every stack page links its neighbours along each axis, so any combination is a click or two from here.
What Supabase Auth gives you
Supabase Auth is a hosted service — GoTrue — that your app reaches over cookies rather than an SDK session object. Credentials and the canonical user records live in Supabase's managed auth.users schema. What lands in your own database is a mirror: db/auth-schema.ts declares user keyed by the Supabase auth uid (text; varchar(255) on MySQL) with email, full name, avatar URL and timestamps, so app-type schemas can foreign-key user exactly as they would under a self-hosted auth. Unlike the Clerk fragment, no sync webhook is emitted here, because Supabase's own trigger mechanism is the intended path: a trigger on auth.users writing into public.user lives in the Supabase project, not in the drizzle migration.
The migration owns the mirror's column shape and nothing else, so wiring that trigger is a step you take before those foreign keys mean anything. The mechanic that actually shapes this adapter is cookie refresh. @supabase/ssr rotates the auth token, and a rotated cookie only reaches the browser if something writes it onto the outgoing response — which is why every framework branch is built around the same getAll/setAll pair, wired to whatever that framework calls a cookie jar. Next's src/proxy.ts rebuilds the NextResponse inside setAll before calling getUser().
React Router has no middleware layer, so app/lib/supabase/server.ts constructs the client per request and returns { supabase, headers }, and the protected layout route attaches those headers to both exits — the redirect and the pass-through — so a refresh that happened during a guard is not lost. Nuxt's server/utils/supabase.ts binds the client to the h3 event and writes through setCookie, with a Nitro middleware doing the guard. Every decision point calls supabase.auth.getUser(), never getSession(): getSession reads whatever the cookie claims, getUser revalidates it against Supabase. Because a browser client is emitted alongside the server one, the auth screens are real forms calling supabase.auth.signInWithPassword rather than a hosted widget, and the sidebar's user menu subscribes to onAuthStateChange. Two consequences to plan around.
The anon key is public by design and is inlined into the client bundle under whichever prefix the framework demands (NEXT_PUBLIC_, VITE_, NUXT_PUBLIC_), so protection has to come from row-level security on Supabase's side, not from keeping the key quiet. And every guard is a network call to Supabase, not a local query — cheap, but not free, and on the path of every protected request.
Built for
src/proxy.ts constructs a server client whose setAll writes the rotated cookies onto a rebuilt NextResponse, then calls supabase.auth.getUser() and redirects when there is no user. Skipping the response rebuild is how refreshed tokens silently fail to reach the browser.
requireUser() in src/lib/session.ts builds the cookie-bound client from src/lib/supabase/server.ts and redirects when getUser() returns no user. getSession() is deliberately not used in any guard — it trusts the cookie instead of checking it.
The (auth)/sign-in screen calls supabase.auth.signInWithPassword through createBrowserClient (src/lib/supabase/client.ts), then router.push plus router.refresh() so the server re-reads the freshly set cookie. signInWithOAuth on the same client adds social providers.
The user mirror in db/auth-schema.ts keys on the auth uid, so app-type user_id columns resolve against it. Populate it from a Supabase trigger on auth.users — the drizzle migration creates the table but never the trigger.
app/lib/supabase/server.ts returns { supabase, headers } per request and the protected React Router layout spreads headers onto both the redirect and the pass-through response; on Nuxt, serverSupabase(event) writes them straight onto the event with h3's setCookie.
Why Supabase Auth, specifically
Hosted: Supabase owns identity in its managed auth.users. This stack emits a LOCAL `user` mirror (db/auth-schema.ts) so app-type schemas can foreign-key `user` directly — keep it in sync with a Supabase trigger on auth.users (insert/update → public.user). The drizzle migration only owns the mirror table's shape, not the trigger.
Sessions are cookie-based (@supabase/ssr): the proxy refreshes them on every request; Server Components read the user via supabase.auth.getUser().
What Supabase Auth pulls in
bun add @supabase/ssr @supabase/supabase-jsNEXT_PUBLIC_SUPABASE_URLNext: your Supabase project URL (RR: VITE_SUPABASE_URL · Nuxt: NUXT_PUBLIC_SUPABASE_URL)NEXT_PUBLIC_SUPABASE_ANON_KEYNext: the project's anon/public key (RR: VITE_SUPABASE_ANON_KEY · Nuxt: NUXT_PUBLIC_SUPABASE_ANON_KEY)SUPABASE_URLReact Router server-side (loaders): same project URL, read off process.env — never inlined into the client bundleSUPABASE_ANON_KEYReact Router server-side: same anon key