---
name: set-up-supabase-auth
description: Sets up Supabase authentication (email magic link, password or Google OAuth) in a Next.js app with server-side sessions, protected routes and row level security. Use when the user wants login, sign in, sign up, user accounts, auth, or to protect pages and data per user.
license: CC-BY-4.0
compatibility: Requires a Supabase project and Node 20+
metadata:
  category: backend
  tags: "supabase,auth,nextjs,postgres"
  author: johnny-bravo
  version: "1.1"
  updated: "2026-10-31"
  status: published
---

# Set up Supabase auth

Supabase Auth handles sign-up, sign-in and sessions for you. In Next.js the session lives in cookies, so the server and the browser both need their own Supabase client. This skill wires that up and protects pages and data per user.

## When to use
- Adding login or sign-up to a Next.js App Router project backed by Supabase.
- Adding Google or another OAuth provider, or email magic links.
- Making pages or data visible only to the signed-in user.

## Steps
1. `npm i @supabase/supabase-js @supabase/ssr`; add `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` (the publishable key) to `.env.local`.
2. Create two clients: `createBrowserClient` for client components and `createServerClient` (with Next `cookies()`) for server components, route handlers and server actions.
3. Add `middleware.ts` (called `proxy.ts` in Next.js 16) that refreshes the session on each request and redirects signed-out users away from `/app/*`.
4. Enable providers in the Supabase dashboard (Email, Google). Set the Site URL and redirect URLs for localhost and production.
5. Add an `/auth/callback` route that calls `exchangeCodeForSession(code)`.
6. Turn on row level security for every table and add policies such as `using ((select auth.uid()) = user_id)`. For full schema, policy and index guidance, use `design-supabase-postgres-schema`.
7. On the server, trust `supabase.auth.getUser()` or `getClaims()` (they verify the token), not `getSession()`.
8. Test with two accounts: sign up, sign out, sign in again, and confirm each user sees only their own data.

## Gotchas
- RLS on with no policies means every query returns zero rows. That is the usual "it worked yesterday" bug.
- Magic links fail if the redirect URL is not allow-listed.
- Session refresh in middleware must return the response with the updated cookies, or users get signed out at random.

## Safety
- Never ship the `service_role` or secret key to the browser or prefix it with `NEXT_PUBLIC_`. It bypasses RLS.
- Keep `.env.local` out of git. Never paste keys or user passwords into chat or logs.
- Do not store passwords yourself. Supabase Auth hashes them for you.

## Works with other stacks
The same steps work with Remix, SvelteKit or Nuxt, since `@supabase/ssr` supports any cookie-based server framework. If you use Clerk, Auth.js or Firebase Auth instead, the shape is equivalent: server-side session check, protected routes, per-user data rules.

## Done when
- Sign-up, sign-in and sign-out work on localhost and production URLs.
- Signed-out visitors are redirected away from protected pages.
- RLS is on, and a second test user cannot see the first user's data.

## Example prompt
"Add Google login with Supabase and make the dashboard only visible to signed-in users."
