# Les 18 — Supabase Auth + RLS ## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven, **laatste les!**) **Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk. **Demo-app:** Multi-user "Tasks" app met magic-link login + RLS **Vervolg op:** Les 17 — Externe APIs in diepte **Aansluit op:** Eindopdracht (thuis) --- ## Slide 1: Title ### Les 18 — Supabase Auth + RLS **Visual:** "Les 18" BLUE, "Supabase Auth + RLS" BLACK, subtitle "Multi-user apps met per-user data isolation — productie ready" --- ## Slide 2: Terugblik + Curriculum recap ### Waar staan we? **18 lessen — wat we hebben gedaan:** - Les 1-10: Foundations (Next.js, Supabase basics, Tailwind, Shadcn, deploy basics, Auth intro) - Les 11: AI SDK basics + Polderfest - Les 12: Tool Calling - Les 13: Agents - Les 14: RAG + Embeddings - Les 15: Cursor + Vercel deploy + GitHub Actions CI - Les 16: MCP servers - Les 17: Externe APIs in diepte - **Les 18: Vandaag — Supabase Auth + RLS multi-user** **Vandaag:** alles wat je hebt geleerd, multi-user maken. Klaar voor je eindopdracht. --- ## Slide 3: Planning ### Vandaag — 180 minuten | Onderwerp | Duur | |-----------|------| | Terugblik + waarom auth | 10 min | | Theorie: Supabase Auth basics | 20 min | | Theorie: RLS — wat en waarom | 20 min | | Theorie: Session in Next.js | 15 min | | **Live Demo 1** — Auth setup + magic link | 25 min | | **Live Demo 2** — Middleware + protected routes | 20 min | | **Pauze** | 15 min | | **Live Demo 3** — RLS policies multi-user | 30 min | | **Live Demo 4** — Social login (GitHub) | 15 min | | Eindopdracht recap | 5 min | | Vragen + afsluiting cyclus | 15 min | --- ## Slide 4: Waarom auth + multi-user ### Echte apps hebben users **Wat je niet kunt zonder auth:** - Per-user data (mijn tasks, jouw tasks) - Privacy (alleen jij ziet je data) - Audit trail (wie deed wat wanneer) - Subscriptions (Les 17 — wie betaalde, wie niet) **Auth = 'wie ben jij?'** **Authz = 'wat mag jij?'** In Supabase: Auth + RLS doen beide. Geïntegreerd, simpel, productie-ready. **Voor je eindopdracht:** als je app ÉÉN gebruiker heeft (bv. demo), kun je auth skippen. Maar als je app meerdere users heeft → must have. --- ## Slide 5: Supabase Auth basics ### Drie methoden **1. Email + password (klassiek):** - User registreert met email + wachtwoord - Confirmation email - Reset-flow voor vergeten wachtwoord **2. Magic link (passwordless):** - User vult email in → krijgt link in mail → klikt → ingelogd - Geen wachtwoord te onthouden / lekken - Modern, secure, simpel **3. Social (OAuth):** - "Login met Google", "Login met GitHub" - User klikt → OAuth flow → Supabase regelt sessie - Geen account-creation nodig **Voor demo: magic link.** Snel, geen wachtwoord-hassle, productie-grade. ```typescript await supabase.auth.signInWithOtp({ email: "user@example.com", }); ``` --- ## Slide 6: RLS — wat en waarom ### Row Level Security **Het probleem zonder RLS:** ```sql -- Anyone with anon key can query everything SELECT * FROM tasks; -- Returns ALL tasks of ALL users — disaster ``` **Met RLS:** ```sql -- Same query, maar Postgres filtert automatisch: SELECT * FROM tasks WHERE user_id = auth.uid(); -- Returns alleen JOUW tasks ``` **RLS = filter dat Postgres ALTIJD toepast op queries**, ongeacht waar de query vandaan komt (client, server, anonymous, authenticated). **Setup:** ```sql alter table tasks enable row level security; create policy "users see own tasks" on tasks for select using (auth.uid() = user_id); create policy "users insert own tasks" on tasks for insert with check (auth.uid() = user_id); ``` **`auth.uid()`** is Supabase's helper — geeft de ingelogde user ID, of NULL als anonymous. --- ## Slide 7: Session in Next.js ### Cookies, middleware, server components **Supabase Auth in Next.js (App Router):** ```bash pnpm add @supabase/supabase-js @supabase/ssr ``` **Vier files:** 1. `utils/supabase/client.ts` — voor client components 2. `utils/supabase/server.ts` — voor server components + actions 3. `utils/supabase/middleware.ts` — refresh sessie elke request 4. `middleware.ts` (root) — gebruikt #3 **Waarom de complexiteit:** - Cookies moeten gesynced tussen browser + server - Sessies verlopen — middleware refreshed - Voor server-rendering: auth state moet vooraf bekend zijn Supabase heeft een **template** voor exact deze setup. Kopiëren werkt, snappen waarom is bonus. --- ## Slide 8: Wat we vandaag bouwen ### Multi-user Tasks app **Doel:** simpele tasks-app, ieder z'n eigen lijst, geen vermenging. **Tech:** - Next.js 16 - Supabase Auth (magic link + GitHub OAuth) - Supabase Postgres met RLS - Tailwind + Shadcn **Features:** - `/login` — magic link form + GitHub button - `/` — task list (alleen ingelogd) - Add / complete / delete task — RLS bepaalt scope - `/logout` — logout button **Schema:** ```sql create table tasks ( id bigserial primary key, user_id uuid not null references auth.users(id), text text not null, done boolean default false, created_at timestamp default now() ); ``` --- ## Slide 9: LIVE DEMO 1 — Auth setup + magic link ### ~25 min **Wat ik laat zien:** 1. `pnpm create next-app tasks-app + add @supabase/ssr @supabase/supabase-js` 2. Supabase project: Auth → Email → enable Magic Links 3. `utils/supabase/server.ts` + `client.ts` (template van Supabase docs) 4. `app/login/page.tsx`: ```tsx "use client"; import { createClient } from "@/utils/supabase/client"; export default function Login() { async function signIn(formData: FormData) { const supabase = createClient(); await supabase.auth.signInWithOtp({ email: formData.get("email") as string, options: { emailRedirectTo: `${location.origin}/auth/callback` }, }); alert("Check je email!"); } return
; } ``` 5. `app/auth/callback/route.ts` — exchange code voor sessie 6. Test: email invullen → mail krijgen → klikken → ingelogd --- ## Slide 10: LIVE DEMO 2 — Middleware + protected routes ### ~20 min **Wat ik laat zien:** 1. `middleware.ts` root: ```typescript import { type NextRequest } from "next/server"; import { updateSession } from "@/utils/supabase/middleware"; export async function middleware(request: NextRequest) { return await updateSession(request); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], }; ``` 2. Server Component check: ```tsx import { createClient } from "@/utils/supabase/server"; import { redirect } from "next/navigation"; export default async function Home() { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) redirect("/login"); return