Files
novi-lessons/Les18-Supabase-Auth-RLS/Les18-Docenttekst.md
2026-06-03 16:58:25 +02:00

13 KiB

Les 18 — Supabase Auth + RLS

Docenttekst (Klas A — 3 uur, fysiek, demo-driven, laatste les)

Les: 18 van 18 Onderwerp: Multi-user apps met magic link + RLS + social login Duur: 180 minuten Demo-app: tasks-app — multi-user tasks met RLS


VÓÓR DE LES (60 min — meer tijd voor laatste les setup)

  1. Werkende tasks-app op laptop (magic link + GitHub + RLS)
  2. Twee email-accounts klaar voor multi-user demo (Tim's eigen + tweede test-mail)
  3. GitHub OAuth App aangemaakt + Supabase configured
  4. Backup screenshots: magic link in inbox, RLS policy editor, multi-user side-by-side
  5. Speciaal voor laatste les: bedank-slide klaar, eindopdracht reminder
  6. Browser tabs: Supabase Dashboard (2 projecten, 1 demo + 1 backup), GitHub OAuth Apps, Vercel
  7. Mentaal voorbereid: dit is afsluiting cyclus, ruimte voor terugkijken + dank

HET SCRIPT

BLOK 1 — Welkom + recap 18 lessen (10 min)

[SLIDE 1]

Vertel: "Welkom bij de allerlaatste les. Les 18. Vandaag: Supabase Auth en RLS. En aan het eind: terugkijken op 18 lessen samen."

[SLIDE 2 — Curriculum recap]

Vertel: "Even kort. We hebben vandaag de stof voor jullie eindopdracht compleet. Lessen 1 tot 10 — foundations. Next.js, Supabase, Tailwind, basics. Lessen 11 tot 14 — AI: SDK, tool calling, agents, RAG. Les 15 — Cursor en Vercel. Les 16 — MCP. Les 17 — externe APIs in diepte. Vandaag — multi-user maken.

Na vandaag heb je álle bouwstenen voor je eindopdracht. Niet meer 'volgende les leer ik nog X'. Vanaf morgen — bouwen."

[SLIDE 3 — Planning]

Vertel: "Drie uur. 65 min theorie en 4 demos. Pauze rond minuut 100. Aan het eind: 15 minuten ruimte voor terugkijken en vragen over eindopdracht."


BLOK 2 — Theorie (55 min)

[SLIDE 4 — Waarom auth]

Vertel: "Waarom auth. Wat kun je niet zonder. Per-user data — mijn tasks, jouw tasks. Privacy — alleen jij ziet je data. Audit trail — wie deed wat. Subscriptions — wie betaalde, wie niet.

Twee concepten die mensen vaak verwarren. Auth — 'wie ben jij'. Authz — 'wat mag jij'. Login is auth. Permissies zijn authz.

Supabase doet beide. Auth via magic link of OAuth. Authz via RLS. Geïntegreerd, simpel."

[SLIDE 5 — Auth methoden]

Vertel: "Drie methoden. Email + password — klassiek, werkt, maar wachtwoord-management. Magic link — passwordless, user vult email in, krijgt link, klikt, ingelogd. Modern. Social OAuth — login met Google of GitHub.

Voor demo vandaag: magic link én GitHub OAuth. Beide werken naast elkaar in dezelfde app. User kiest."

[SLIDE 6 — RLS]

Vertel: "Nu het belangrijkste. RLS. Row Level Security. Zonder dit kan iedereen met je anon key letterlijk alle data ophalen uit alle tabellen. Disaster.

Met RLS: Postgres past automatisch een filter toe. Welke filter? Wat jij definieert in een policy.

*[Wijs naar code op slide]*

alter table tasks enable row level security — RLS aan.

create policy "users see own tasks" on tasks for select using (auth.uid() = user_id) — alleen rows waar user_id matched de ingelogde user mogen geselecteerd worden.

auth.uid() is een Supabase helper — geeft de UUID van ingelogde user, of NULL. Postgres weet wie ingelogd is door de JWT in de Authorization header."

💬 Vraag: 'Werkt RLS ook vanuit server-side calls?'

Antwoord: "Ja, mits je client met de user-JWT calls maakt. Voor server-side acties als admin gebruik je service-role key — die bypasst RLS. Voor user-actions vanuit je server: gebruik de user's session client, RLS werkt zoals verwacht. Komt straks in demo."

[SLIDE 7 — Session in Next.js]

Vertel: "Eén stuk complexiteit. Supabase Auth in Next.js heeft een vier-file pattern. Client, server, middleware, en root middleware.

Waarom complex. Cookies moeten gesynced. Sessies verlopen. Server components hebben aparte clients dan client components.

Goed nieuws — Supabase heeft een template voor exact deze setup. Kopiëren werkt. Snappen waarom is bonus. Vandaag kopieer ik en leg uit waarom elke file doet wat-ie doet."


[SLIDE 8] + [SLIDE 9] [SCHERM: terminal + editor]

cd ~/novi/novi-lessons/Les18-Supabase-Auth-RLS
pnpm create next-app@latest tasks-app --typescript --tailwind --app
cd tasks-app
pnpm add @supabase/supabase-js @supabase/ssr

*[Supabase Dashboard — projectsetup]* [SCHERM: browser]

Vertel: "Nieuw Supabase project of bestaande. Auth → Email → Magic Links staat default aan. Site URL: localhost:3000. Redirect URLs ook localhost. Voor productie pas je dit later aan."

*[Editor — maak utils/supabase/client.ts]*

Vertel: "Eerst de client. Voor Client Components. createBrowserClient uit @supabase/ssr. Wrap in een createClient() functie. Klaar."

*[utils/supabase/server.ts]*

Vertel: "Server-side variant. Belangrijk verschil: leest cookies via Next.js cookies() API. Net iets meer code. Belangrijk: await cookies() in Next 15+."

*[app/login/page.tsx — magic link form]*

"use client";
const supabase = createClient();
await supabase.auth.signInWithOtp({
  email,
  options: { emailRedirectTo: `${location.origin}/auth/callback` },
});

*[app/auth/callback/route.ts]*

Vertel: "Callback route. User klikt magic link → Supabase redirect naar dit endpoint met ?code=xyz. We exchangen die code voor een sessie."

*[Test in browser]*

Vertel: "Email invullen, click. Check mail. Magic link mail komt binnen — onderwerp 'Confirm your signup'. Klik link."

*[Browser opent /auth/callback en redirect naar /]*

Vertel: "Sessie actief. Cookie geset. Maar — we hebben nog geen home page met user-data. Doen we volgende demo."


BLOK 4 — DEMO 2: Middleware + protected (20 min)

[SLIDE 10] [SCHERM: editor]

*[utils/supabase/middleware.ts]*

Vertel: "Middleware. Draait elke request. Refreshed de sessie zodat user niet om de minuut wordt uitgelogd. Code is template-werk, kopiëren mag."

*[middleware.ts root]*

Vertel: "Root middleware. Importeert updateSession uit utils, runt 'm. Matcher excludeert static assets — performance."

*[app/page.tsx — protected]*

const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect("/login");

Vertel: "Server Component check. auth.getUser() returnt of user is ingelogd. Geen user? Redirect naar login. Wel? Render welkom-bericht."

*[Test]*

Vertel: "Niet ingelogd — gaat naar login. Inloggen — terug naar home. Werkt. Inkomende vraag uit publiek?"

💬 Vraag: 'Hoe weet middleware welke user ingelogd is?'

Antwoord: "Cookies. Browser stuurt Supabase auth cookies bij elke request. Middleware reads cookie, parseerd JWT, doet getUser() om te valideren bij Supabase. Resultaat cached, sessie refreshed indien nodig."


BLOK 5 — Pauze (15 min)

[SLIDE 11]

Vertel: "Pauze. Vijftien minuten. Laatste pauze van het hele vak."


BLOK 6 — DEMO 3: RLS policies (30 min)

[SLIDE 12] [SCHERM: editor + supabase + browser]

Vertel: "Nu het hart van vandaag. RLS. Multi-user."

*[Supabase SQL Editor]*

create table tasks (
  id bigserial primary key,
  user_id uuid not null references auth.users(id) default auth.uid(),
  text text not null,
  done boolean default false,
  created_at timestamp default now()
);

alter table tasks enable row level security;

Vertel: "Tasks tabel. user_id met default auth.uid() — bij insert wordt user_id automatisch de ingelogde user. RLS aanzetten."

create policy "select own" on tasks for select using (auth.uid() = user_id);
create policy "insert own" on tasks for insert with check (auth.uid() = user_id);
create policy "update own" on tasks for update using (auth.uid() = user_id);
create policy "delete own" on tasks for delete using (auth.uid() = user_id);

Vertel: "Vier policies. Select, insert, update, delete. using = check tijdens read. with check = check tijdens write. Default-deny: zonder policies kan niemand iets."

*[app/page.tsx aanvullen met task UI]*

*[Test in browser — voeg task toe]*

Vertel: "Werkt. Task in database. Maar — staat user_id daar? Open Supabase Table Editor."

*[Toon — user_id is auto-filled met auth.uid()]*

Vertel: "Magic. Default in schema deed het."

*[Open incognito tab — login met andere mail]*

Vertel: "Tweede gebruiker, andere browser-context. Home page laadt — geen tasks. Even niet vermengd. Voeg ander task toe."

*[Toggle terug naar eerste tab]*

Vertel: "Eerste gebruiker. Refresh. Eigen tasks alleen. Werkt. Multi-user isolation."

*[Demo: probeer expliciet andermans data te halen]*

// In Console of via direct supabase call:
const { data } = await supabase.from("tasks").select("*");
// Krijgt ALLEEN eigen tasks

Vertel: "Postgres past de filter ALTIJD toe. Geen workaround vanuit client-side. RLS is sterke garantie."


BLOK 7 — DEMO 4: Social login (15 min)

[SLIDE 13] [SCHERM: github + supabase + editor]

Vertel: "Eén methode is genoeg, maar laat ik GitHub OAuth ook tonen. User-friendlier en common."

*[GitHub Settings → Developer settings → OAuth Apps]*

Vertel: "New OAuth App. Naam: tasks-app. Homepage: localhost:3000. Callback: https://[project-id].supabase.co/auth/v1/callback — die URL krijg je van Supabase."

*[Save, generate Client Secret]*

*[Supabase Dashboard → Auth → Providers → GitHub]*

Vertel: "Enable, plak Client ID, plak Secret, save."

*[app/login/page.tsx — voeg GitHub button toe]*

async function signInWithGitHub() {
  await supabase.auth.signInWithOAuth({
    provider: "github",
    options: { redirectTo: `${location.origin}/auth/callback` },
  });
}

*[Test — klik GitHub button]*

Vertel: "Redirect naar GitHub. Klik Authorize. Terug. Ingelogd als GitHub user. RLS werkt zelfde — GitHub user heeft eigen auth.uid(), eigen tasks."

💬 Vraag: 'Als ik magic link met test@mail.com gebruik én GitHub-account met dezelfde mail — twee verschillende users?'

Antwoord: "Default: ja, twee aparte users. Supabase heeft 'Link Identity' optie om same-email users te mergen. Stel je in Auth settings. Voor demo: niet doen, complicates. Productie: meestal wel."


BLOK 8 — Eindopdracht recap (5 min)

[SLIDE 14]

Vertel: "Eindopdracht. Tijd om concreet te worden. Wat heb je nodig:

Next.js met TypeScript en Tailwind — eerste 10 lessen. Supabase Postgres met Auth en RLS — vandaag. AI SDK met Tool Calling of Agent — lessen 11 tot 13. Eventueel RAG — les 14. Cursor en Vercel deploy — les 15. Externe API — les 17. Alles geïntegreerd in een multi-user app — vandaag.

Ideale eindopdracht: een app die mensen écht zouden gebruiken. Voor jezelf, je ouders, je vereniging. Doet één ding goed. Heeft AI als kern-feature, niet als afterthought. Multi-user met auth. Deployed met productie URL.

Tijd: 40 tot 60 uur thuis, verspreid over 4 tot 6 weken. Niet één weekend van 80 uur. Push klein en vaak. Vraag hulp tijdig."


BLOK 9 — Afsluiting cyclus (15 min)

[SLIDE 15]

Vertel: "Achttien lessen. Achtenvijftig uur klassikaal samen. Plus jullie thuiswerk. Een vol vak.

Even terugkijken. Les 1 — pnpm create-next-app. Vandaag — multi-user app met RLS, AI agents, Vercel preview deploys. Dat is een hele weg.

Wat je nu zelf kunt — een productie-app van scratch bouwen. AI integreren als kern, niet als gimmick. Externe APIs verwerken met OAuth en webhooks. Multi-user met auth en RLS. Code reviews via PR's. CI/CD pipelines opzetten.

Drie jaar geleden was dit een specialist-skill. Nu — basis. En jullie zijn er.

Voor je eindopdracht: werk er gestaag aan. Niet laatste week. Push klein en vaak. Vraag hulp via Slack, mail, of office hours. Eindopdracht-presentatie volgt na inleveren.

Bedankt voor jullie inzet. Het was een genoegen om met jullie te werken. Veel succes met de eindopdracht. En als je iets bouwt waar je trots op bent — laat het me weten. Echt waar.

Vragen?"

*[Vragenronde — laat dit echt open. 10+ min over laten als kan]*


NA DE LES

  • Push tasks-app referentie naar GitHub
  • Brightspace: eindopdracht-instructies + referentie-repos van alle 18 lessen
  • Email-template klaar voor "office hours" tijdens eindopdracht-fase
  • Voor jezelf: noteer welke onderdelen meer of minder werkten — voor Klas B / volgende cyclus

Veelvoorkomende fouten

Fout Oplossing
cookies is not a function await cookies() in Next 15+
Magic link mail komt niet Check spam, productie SMTP via Resend
RLS error op insert Default auth.uid() op user_id kolom, met check policy
OAuth callback 404 Auth → URL Configuration: callback URL exact match
Tasks van anderen zichtbaar RLS niet aan? enable row level security
Loop login → home → login Middleware werkt niet correct — check matcher
Productie sessie verloopt direct Site URL in Supabase niet bijgewerkt voor productie URL