Authentication

Authentication

Authentication is handled entirely by Supabase Auth. Supabase is used only for auth — never for database queries.

Authorization is enforced by PostgreSQL Row Level Security (RLS).


Supabase Clients

The foundation uses three separate Supabase clients, each for a specific environment:

ClientFileUsed For
Browserlib/supabase/client.tsLogin/logout, session management, auth state changes
Serverlib/supabase/server.tsResolve user/session from cookies, extract JWT
Edgelib/supabase/middleware.tsSession validation, routing decisions

Environment Variables

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=

Auth Flow

  1. User logs in via the Supabase browser client
  2. Session is stored in cookies
  3. Server calls requireUser():
    • getUser() — authenticates with Supabase Auth server (trusted)
    • getSession() — obtains access_token (only after getUser succeeds)
  4. JWT is passed to PostgreSQL via withAuthDb()
  5. RLS enforces authorization

getUser() vs getSession()

This is a critical distinction:

MethodWhat it doesTrustworthy?
getUser()Contacts Supabase Auth serverYes — server-verified
getSession()Reads from cookiesNo — data may not be authentic

Rule: Always verify auth with getUser() first. Only use getSession() to get the access_token after a successful getUser() call.

// ✅ Correct (requireUser does this internally)
const { data: { user } } = await supabase.auth.getUser();
if (!user) return unauthorized();
const { data: { session } } = await supabase.auth.getSession();

// ❌ Wrong — trusting session without verification
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) { /* INSECURE */ }

User Registration Trigger

When a user registers, a PostgreSQL trigger automatically:

  1. Creates a record in public.users
  2. Creates a personal tenant (workspace)
  3. Adds the user as tenant owner

This is defined in: supabase/migrations/0003_user_tenant_trigger.sql


Rules

  1. Never use next-auth or any other auth provider
  2. Never use Supabase for database queries
  3. Never expose the service role key
  4. Auth logic stays server-side
  5. Always use getUser() to verify auth — never trust getSession() alone
  6. Use requireUser() in all protected routes (it handles getUser + getSession correctly)

On this page