Supabase Auth — Authentication Setup
Add authentication to a Next.js + Supabase project.
Interview
- Roles: user only / user + teacher / user + teacher + admin
- Registration method: Email+password / Magic Link / OAuth (Google, etc.)
- Teacher code: Require a code for teacher registration?
- Email confirmation: Required / Not required (recommend OFF during development)
What Gets Built
1. Supabase Client Helpers
src/lib/supabase/
├── client.ts # Browser client (createBrowserClient)
├── server.ts # Server Component / Route Handler
└── middleware.ts # Middleware client
2. Middleware
src/middleware.ts
- Refresh auth sessions
- Redirect unauthenticated users (to
/login) - Define public routes (
/,/login,/signup,/api/webhook, etc.)
3. Auth Pages
src/app/(auth)/
├── login/page.tsx # Login (email + password)
├── signup/page.tsx # Registration (with role selection)
└── callback/route.ts # OAuth / Magic Link callback
4. Profile Table + RLS
create table profiles (
id uuid primary key references auth.users(id) on delete cascade,
role text default 'user' check (role in ('user', 'teacher', 'admin')),
display_name text,
created_at timestamptz default now()
);
alter table profiles enable row level security;
-- View and update own profile only
create policy "Users can view own profile"
on profiles for select using (auth.uid() = id);
create policy "Users can update own profile"
on profiles for update using (auth.uid() = id);
-- Auto-create on registration (trigger)
create or replace function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, role, display_name)
values (new.id, 'user', new.raw_user_meta_data->>'display_name');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();
5. Supabase Dashboard Settings
- Site URL: Your production URL
- Redirect URLs:
http://localhost:3000/**,https://{domain}/** - Email confirmation: Recommend OFF during development
- Email templates: Customize as needed
Role-Based Access Pattern
// Check role on the server side
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
const { data: profile } = await supabase
.from('profiles')
.select('role')
.eq('id', user.id)
.single()
if (profile.role !== 'teacher') redirect('/dashboard')