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:
| Client | File | Used For |
|---|---|---|
| Browser | lib/supabase/client.ts | Login/logout, session management, auth state changes |
| Server | lib/supabase/server.ts | Resolve user/session from cookies, extract JWT |
| Edge | lib/supabase/middleware.ts | Session validation, routing decisions |
Environment Variables
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=Auth Flow
- User logs in via the Supabase browser client
- Session is stored in cookies
- Server calls
requireUser():getUser()— authenticates with Supabase Auth server (trusted)getSession()— obtainsaccess_token(only aftergetUsersucceeds)
- JWT is passed to PostgreSQL via
withAuthDb() - RLS enforces authorization
getUser() vs getSession()
This is a critical distinction:
| Method | What it does | Trustworthy? |
|---|---|---|
getUser() | Contacts Supabase Auth server | Yes — server-verified |
getSession() | Reads from cookies | No — 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:
- Creates a record in
public.users - Creates a personal tenant (workspace)
- Adds the user as tenant owner
This is defined in: supabase/migrations/0003_user_tenant_trigger.sql
Rules
- Never use
next-author any other auth provider - Never use Supabase for database queries
- Never expose the service role key
- Auth logic stays server-side
- Always use
getUser()to verify auth — never trustgetSession()alone - Use
requireUser()in all protected routes (it handlesgetUser+getSessioncorrectly)