Supabase Authentication

End-to-end auth with email/password, magic links, and session management.

Overview

Catalyst supports multiple auth modes. When Supabase is configured, you get full authentication with registration, password reset, email verification, and protected routes.

What's included:

  • Sign in, Register, Forgot Password, Reset Password pages
  • Session management with automatic refresh
  • Protected /app/* routes via middleware
  • Sign out functionality
  • Dynamic redirect URLs for multi-environment support

Step 1: Create Supabase Project

  1. Go to supabase.com/dashboard
  2. Click New project
  3. Choose your organization, name your project, set a database password
  4. Select a region close to your users
  5. Wait for the project to finish provisioning (~2 minutes)

Step 2: Get API Keys

  1. In your Supabase project, go to Project Settings → API
  2. Copy the Project URL → this is your NEXT_PUBLIC_SUPABASE_URL
  3. Copy the anon/public key → this is your NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY

⚠️ Important

Never use the service_role key in the browser. It bypasses Row Level Security and should only be used server-side.

Step 3: Configure Redirect URLs

Supabase needs to know which URLs are allowed for auth redirects (email confirmation links, password reset links, etc.).

  1. Go to Authentication → URL Configuration
  2. Set Site URL to your production URL:https://your-domain.com
  3. Add these to Redirect URLs:

http://localhost:3000/**

https://your-domain.com/**

https://*.vercel.app/**

The ** wildcard allows any path on that domain.

Step 4: Configure Environment Variables

Add these to your .env.local file:

# Supabase

NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co

NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=eyJhbGc...

# App URL (for production)

NEXT_PUBLIC_APP_URL=https://your-domain.com

Leave NEXT_PUBLIC_APP_URL unset for local development — it defaults to http://localhost:3000.

Step 5: Customize Email Templates

Supabase sends emails for signup confirmation, password reset, etc. Customize them to match your brand.

  1. Go to Authentication → Emails (under Notifications)
  2. Select a template (e.g., Confirm sign up)
  3. Edit the Subject and Body

Suggested template for "Confirm sign up":

# Subject

Catalyst - Confirm Account

# Body

<h3>Hi there,</h3>

<p>Thanks for registering an account.</p>

<p>Follow this link to confirm your email:</p>

<ul>

<li><a href="{{ .ConfirmationURL }}">Confirm Email Address</a></li>

</ul>

<p>These are your account details:</p>

<ul>

<li>Email: {{ .Email }}</li>

<li>App: {{ .SiteURL }}</li>

</ul>

Available variables: {{ .ConfirmationURL }}, {{ .Email }}, {{ .SiteURL }}, {{ .Token }}, {{ .TokenHash }}, {{ .Data }}, {{ .RedirectTo }}

Step 6: Optional Auth Settings

In Authentication → Sign In / Providers, you can configure:

  • Allow new users to sign up — Whether new users can register via Supabase
  • Confirm email — Enable for production, disable for faster dev testing
  • Secure email change — Requires confirmation for email changes
  • Password requirements — Set minimum length, complexity

Step 7: Create a Test User

You have two options:

Supabase Dashboard

  1. Go to Authentication → Users
  2. Click Add user
  3. Enter email and password
  4. User is pre-confirmed

Register Page

  1. Start your app: pnpm dev
  2. Go to /auth/register
  3. Create an account
  4. Confirm via email (if enabled)

Step 8: Test the Flow

  1. Start the dev server: pnpm dev
  2. Go to /auth/login — you should see the sign in form
  3. Sign in with your test user
  4. You should be redirected to /app
  5. Try accessing /app while signed out — you should be redirected to sign in

How It Works

/auth/login

Calls supabase.auth.signInWithPassword(). On success, Supabase sets session cookies automatically.

/auth/register

Calls supabase.auth.signUp(). If email confirmation is enabled, sends email with link to /api/auth/callback. Otherwise, logs in immediately.

/api/auth/callback

Handles Supabase email links. Exchanges the auth code for a session and redirects to the app.

proxy.ts

Refreshes session on every request. Protects /app/* routes — redirects unauthenticated users to sign in.

/auth/signout

Signs out the user and clears session cookies. Redirects to sign in.

Adding a Sign Out Button

Add a sign out link anywhere in your app:

<a href="/auth/signout">Sign out</a>

Or use a Link component:

import Link from "next/link"

<Link href="/auth/signout">Sign out</Link>

Deployment Checklist

  • Set NEXT_PUBLIC_APP_URL to your production domain in Vercel/hosting environment variables
  • Add your production domain to Supabase Redirect URLs
  • Update Supabase Site URL to your production domain
  • Enable Confirm email for production
  • Enable Row Level Security on tables with user data (see below)

Security: Row Level Security (RLS)

RLS controls which rows users can access in your database. Without it, any authenticated user can read/write all data.

When to enable RLS

POC/Development: Keep RLS off for fast iteration — you can query freely without policies.
Before Production: Enable RLS and add policies — this is a security requirement.

Why RLS matters:

❌ Without RLS

User A can query SELECT * FROM posts and see ALL posts from ALL users.

✓ With RLS

User A can only see their own posts. The policy enforces user_id = auth.uid().

How to enable RLS:

  1. Go to Database → Tables in Supabase dashboard
  2. Select your table (e.g., posts)
  3. Click RLS DisabledEnable RLS
  4. Add a policy (see examples below)

Common policy patterns:

-- Users can only view their own rows

CREATE POLICY "Users view own data" ON posts

FOR SELECT USING (auth.uid() = user_id);

-- Users can insert rows for themselves

CREATE POLICY "Users insert own data" ON posts

FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Users can update/delete their own rows

CREATE POLICY "Users modify own data" ON posts

FOR ALL USING (auth.uid() = user_id);

-- Public read, authenticated write

CREATE POLICY "Public read" ON articles FOR SELECT USING (true);

CREATE POLICY "Auth write" ON articles FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);

Tip: Supabase dashboard has a policy editor with templates. Go to Authentication → Policies for a visual interface.

Realtime Notifications (In Development)

The header notifications include a Supabase Realtime broadcast demo that shows toast alerts when public/private messages arrive.

Public Alpha Note

Supabase DB-triggered broadcasts are still Public Alpha. Once approved, switch to database-triggered events on the notifications table instead of manual channel.send() calls.

# .env.local

NEXT_PUBLIC_SUPABASE_REALTIME_ENABLED=true

# Client usage

const channel = supabase.channel("app.public", { config: { broadcast: { self: true } } })

.on("broadcast", { event: "activity.notification" }, () => { /* toast */ })

.subscribe()

await channel.send({

type: "broadcast",

event: "activity.notification",

payload: { scope: "public", activityId: 123 },

})

Demo implementation lives in components/shared/user-notifications.tsx.

Troubleshooting

Email link redirects to wrong URL

Check that your domain is in the Redirect URLs list in Supabase. Make sure NEXT_PUBLIC_APP_URL is set correctly.

Session not persisting

Ensure the middleware is running (check middleware.ts exists at project root). Check browser cookies are enabled.

"Invalid login credentials"

User may not be confirmed. Check Authentication → Usersin Supabase dashboard. Try creating a user via the dashboard.