diff --git a/Les09-Docenttekst.md b/Les09-Docenttekst.md
new file mode 100644
index 0000000..c457b60
--- /dev/null
+++ b/Les09-Docenttekst.md
@@ -0,0 +1,552 @@
+# Les 9 — Supabase Auth
+## Docenttekst
+
+**Les:** 9 van 18
+**Onderwerp:** Supabase Authentication (signUp, signIn, signOut, middleware, RLS)
+**Duur:** 120 minuten
+**Vorige les:** Les 8 — Students hebben Supabase gekoppeld, /create pagina werkend, Server Component patroon, polls database
+
+---
+
+## Leerdoelen
+- Authenticatie vs autorisatie begrijpen
+- Supabase Auth functies gebruiken: signUp, signInWithPassword, signOut, getUser
+- Server client (SSR) vs Browser client onderscheiden
+- Middleware voor session refresh implementeren
+- Authenticated Navbar bouwen met getUser
+- Row Level Security (RLS) voor authenticated users toepassen
+
+---
+
+## Lesopbouw & Timing
+
+### 09:00–09:10 | Welkom + Terugblik (10 min)
+**Slides:** 1, 2, 3
+
+Ik start de les. Korte recap van Les 8:
+- Supabase project aangemaakt
+- NEXT_PUBLIC_SUPABASE_URL en ANON_KEY in .env
+- /create pagina with VoteForm component
+- Polls tabel in database met votes
+- "Na vandaag kunnen jullie je app beveiligen met authenticatie"
+
+**Planning tonen (slide 3):**
+- 09:10–10:00: Uitleg Auth concepten + Demo
+- 10:00–10:15: Samen Middleware + Auth Callback bouwen
+- 10:15–10:30: Pauze
+- 10:30–11:30: Zelf Doen (signup, login, logout, Navbar)
+- 11:30–11:45: Vragen
+- 11:45–12:00: Huiswerk + Afsluiting
+
+---
+
+### 09:10–10:00 | Deel 1a: Uitleg Auth Concepten (50 min)
+**Slides:** 4, 5, 6
+
+#### 09:10 | Slide 4: Wat is Auth?
+
+**Vertel:**
+"Authenticatie is: wie ben jij? Login, password, je identiteit bewijzen.
+Autorisatie is: wat mag je doen? Wie mag polls maken? Dit regelen we later met RLS.
+Supabase Auth beheert alles: signUp, login, sessies, JWT tokens."
+
+**Demo:** Open https://supabase.com/dashboard
+- Klik project → Authentication → Providers → Email
+- Laat zien: Disable Email Confirmations is AAN (sneller testen)
+- Zeg: "Students zien zelf deze checkbox na Le 9"
+
+#### 09:20 | Slide 5: Auth Functies
+
+**Vertel:**
+"Vier kern functies in Supabase Auth:
+1. signUp({ email, password }) — Nieuw account
+2. signInWithPassword({ email, password }) — Inloggen
+3. signOut() — Uitloggen
+4. getUser() — Wie is ingelogd?
+
+Hieronder toon ik hoe we deze gebruiken in Next.js."
+
+**Code tonen (slide 5):**
+```typescript
+// signUp
+const { error } = await supabase.auth.signUp({ email, password });
+
+// signIn
+const { error } = await supabase.auth.signInWithPassword({ email, password });
+
+// signOut
+await supabase.auth.signOut();
+
+// getUser (server of browser)
+const { data: { user } } = await supabase.auth.getUser();
+```
+
+#### 09:30 | Slide 6: Server vs Browser Client
+
+**Vertel:**
+"Supabase Auth werkt in twee omgevingen:
+- **Server Client** (Node.js, SSR): via cookies, secure
+- **Browser Client** (React, CSR): via localstorage, minder secure
+
+We gebruiken @supabase/ssr package. Dit handelt beide af."
+
+**Toon twee code blokken naast elkaar (slide 6):**
+
+**Server Client** (middleware, Navbar):
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createSupabaseServerClient() {
+ const cookieStore = await cookies();
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return cookieStore.getAll(); },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch { }
+ },
+ },
+ }
+ );
+}
+```
+
+**Browser Client** (signup, login, logout):
+```typescript
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createSupabaseBrowserClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+```
+
+**Zeg:**
+"Cookies zijn beveiligd. localStorage in browser kan hack worden. Daarom: server client voor getUser in Navbar, browser client voor login forms."
+
+**📌 Slide 6 referentie voor Middleware:**
+
+Middleware zorgt dat de session word gerefresht op elke request:
+```typescript
+// middleware.ts
+export async function middleware(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({ request });
+ const supabase = createServerClient(...);
+ await supabase.auth.getUser();
+ return supabaseResponse;
+}
+```
+
+"Dit zorgt dat je Session JWT token altijd up-to-date is."
+
+---
+
+### 10:00–10:15 | Deel 1b: Samen Coderen (15 min)
+
+#### Stap 1: npm install
+```bash
+npm install @supabase/ssr
+```
+
+#### Stap 2: lib/supabase-server.ts aanmaken
+Voeg dit in (hieronder exact):
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createSupabaseServerClient() {
+ const cookieStore = await cookies();
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return cookieStore.getAll(); },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch { }
+ },
+ },
+ }
+ );
+}
+```
+
+#### Stap 3: lib/supabase-browser.ts aanmaken
+```typescript
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createSupabaseBrowserClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+```
+
+#### Stap 4: middleware.ts (root project)
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { NextResponse, type NextRequest } from "next/server";
+
+export async function middleware(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({ request });
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return request.cookies.getAll(); },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
+ supabaseResponse = NextResponse.next({ request });
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options)
+ );
+ },
+ },
+ }
+ );
+ await supabase.auth.getUser();
+ return supabaseResponse;
+}
+
+export const config = {
+ matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
+};
+```
+
+#### Stap 5: auth/callback route
+`app/auth/callback/route.ts`:
+```typescript
+import { NextResponse } from "next/server";
+import { createSupabaseServerClient } from "@/lib/supabase-server";
+
+export async function GET(request: Request) {
+ const { searchParams, origin } = new URL(request.url);
+ const code = searchParams.get("code");
+ if (code) {
+ const supabase = await createSupabaseServerClient();
+ await supabase.auth.exchangeCodeForSession(code);
+ }
+ return NextResponse.redirect(origin);
+}
+```
+
+**Zeg:** "Dit is standaard Supabase/Next.js boilerplate. Niet allemaal letterlijk begrijpen. Focus op: server vs browser client."
+
+---
+
+### 10:15–10:30 | Pauze
+**Slide 7**
+
+---
+
+### 10:30–11:30 | Deel 2: Zelf Doen (60 min)
+**Slide 8**
+
+Students bouwen nu zelf:
+1. app/signup/page.tsx
+2. app/login/page.tsx
+3. components/LogoutButton.tsx
+4. components/Navbar.tsx (met getUser)
+5. Uitloggen in layout.tsx
+
+**Reference code:**
+
+#### app/signup/page.tsx
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import Link from "next/link";
+
+export default function SignUp() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+
+ const handleSignUp = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setMessage("");
+ const { error } = await supabase.auth.signUp({ email, password });
+ if (error) { setMessage(error.message); }
+ else { setMessage("Account aangemaakt!"); router.push("/login"); }
+ setLoading(false);
+ };
+
+ return (
+
+
Registreren
+
+ {message &&
{message}
}
+
+ Al een account? Inloggen
+
+
+ );
+}
+```
+
+#### app/login/page.tsx
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import Link from "next/link";
+
+export default function Login() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+
+ const handleLogin = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setMessage("");
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
+ if (error) { setMessage(error.message); }
+ else { router.push("/"); router.refresh(); }
+ setLoading(false);
+ };
+
+ return (
+
+
Inloggen
+
+ {message &&
{message}
}
+
+ Nog geen account? Registreren
+
+
+ );
+}
+```
+
+#### components/LogoutButton.tsx
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+
+export function LogoutButton() {
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+ const handleLogout = async () => {
+ await supabase.auth.signOut();
+ router.push("/");
+ router.refresh();
+ };
+ return (
+
+ );
+}
+```
+
+#### components/Navbar.tsx
+```typescript
+import Link from "next/link";
+import { createSupabaseServerClient } from "@/lib/supabase-server";
+import { LogoutButton } from "./LogoutButton";
+
+export async function Navbar() {
+ const supabase = await createSupabaseServerClient();
+ const { data: { user } } = await supabase.auth.getUser();
+ return (
+
+ );
+}
+```
+
+#### app/layout.tsx (updated)
+```typescript
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+import { Navbar } from "@/components/Navbar";
+
+const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
+const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
+
+export const metadata: Metadata = { title: "QuickPoll", description: "Stem op je favoriete opties" };
+
+export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
+```
+
+**Instructies voor students:**
+1. Maak app/signup/page.tsx — form met email/password inputs
+2. Maak app/login/page.tsx — inlog form
+3. Maak components/LogoutButton.tsx — knop die signOut() aanroept
+4. Maak components/Navbar.tsx — toon email als ingelogd, login/signup links anders
+5. Update layout.tsx — voeg `` toe
+
+**Ik loop rond en help. Studenten kunnen stuck raken op:**
+
+#### Veelvoorkomende problemen
+
+| Probleem | Oorzaak | Oplossing |
+|----------|---------|----------|
+| "Module not found: @supabase/ssr" | npm install niet gedaan | `npm install @supabase/ssr` |
+| Navbar toont altijd "Inloggen" | getUser() returns null | Check cookies middleware, browser dev tools |
+| Login werkt niet | Verkeerde credentials | Check Supabase dashboard → Auth Users |
+| "Invalid PKCE flow" | Browser client misconfigured | Zorg dat .env keys correct zijn |
+| Logout werkt niet | signOut() niet wacht | `await supabase.auth.signOut()` |
+| Layout.tsx error: Navbar is async | Navbar is Server Component | `async` is ok, use await in getUser() |
+
+---
+
+#### 11:00 | Check-in: Navbar
+
+Ik check of iedereen Navbar werkend heeft. Zeg:
+"Navbar is een **Server Component** (async). Daarom kunnen we direct getUser() callen zonder hooks. Dit is uniek voor Next.js."
+
+Toon: `const { data: { user } } = await supabase.auth.getUser();`
+
+#### 11:15 | RLS Update
+
+**Vertel:**
+"Nu authenticatie werkt, beveiligen we polls. Wie mag die maken?
+- Anoniem (niet ingelogd): mag zien en stemmen
+- Authenticated (ingelogd): mag polls maken EN zien EN stemmen"
+
+**Stap 1:** Open Supabase dashboard → SQL Editor
+
+**Stap 2:** Voer uit:
+```sql
+ALTER TABLE polls ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY "polls_select_all" ON polls
+ FOR SELECT USING (true);
+
+CREATE POLICY "polls_insert_authenticated" ON polls
+ FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
+
+CREATE POLICY "polls_update_owner" ON polls
+ FOR UPDATE USING (auth.uid() = created_by);
+```
+
+(Zeg: "Auth.uid() is de ID van ingelogde user. NULL als anoniem.")
+
+**Stap 3:** Test in /create:
+- Niet ingelogd: Knop grijs / gedeactiveerd
+- Ingelogd: Knop blauw, poll aanmaken werkt
+- Na uitloggen: Weer grijs
+
+---
+
+### 11:30–11:45 | Vragen & Debugging
+
+Ik loop rond. Studenten kunnen vragen:
+- "Hoe debug ik auth?"
+ - Supabase dashboard → Auth Users
+ - Browser dev tools → Application → Cookies (zoek sb-*)
+- "Hoe reset ik mijn account?"
+ - Dashboard → Auth Users → delete user → registreer opnieuw
+
+---
+
+### 11:45–12:00 | Huiswerk + Afsluiting (15 min)
+**Slides:** 9, 10
+
+**Slide 9: Huiswerk**
+
+1. **Google OAuth (optioneel, moeilijk)**
+ - Supabase dashboard → Auth → Providers → Google
+ - Copy Client ID, Secret
+ - Voeg signInWithOAuth button toe
+
+2. **Profiel pagina (les 10)**
+ - app/profile/page.tsx
+ - Toon user.email, user.id
+ - Update password / email form (kan les 10 zijn)
+
+3. **Maker tonen bij poll (les 10)**
+ - Voeg `created_by` toe aan polls tabel
+ - Toon bij elke poll wie het maakte
+ - Autorisatie: alleen maker mag aanpassen
+
+**Slide 10: Afsluiting**
+
+"Volgende les: Deployment! We zetten je app live op Vercel. Daarna: Google OAuth, profiel, meer RLS."
+
+---
+
+## Extra: Supabase Auth Docs
+https://supabase.com/docs/guides/auth/server-side/nextjs
diff --git a/Les09-Lesopdracht.pdf b/Les09-Lesopdracht.pdf
new file mode 100644
index 0000000..f0f2318
--- /dev/null
+++ b/Les09-Lesopdracht.pdf
@@ -0,0 +1,263 @@
+%PDF-1.4
+%“Œ‹ž ReportLab Generated PDF document (opensource)
+1 0 obj
+<<
+/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R /F5 14 0 R
+>>
+endobj
+2 0 obj
+<<
+/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
+>>
+endobj
+3 0 obj
+<<
+/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
+>>
+endobj
+4 0 obj
+<<
+/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
+>>
+endobj
+5 0 obj
+<<
+/Contents 20 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+6 0 obj
+<<
+/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
+>>
+endobj
+7 0 obj
+<<
+/Contents 21 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+8 0 obj
+<<
+/Contents 22 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+9 0 obj
+<<
+/Contents 23 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+10 0 obj
+<<
+/Contents 24 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+11 0 obj
+<<
+/Contents 25 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+12 0 obj
+<<
+/Contents 26 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+13 0 obj
+<<
+/Contents 27 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+14 0 obj
+<<
+/BaseFont /Symbol /Name /F5 /Subtype /Type1 /Type /Font
+>>
+endobj
+15 0 obj
+<<
+/Contents 28 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+16 0 obj
+<<
+/Contents 29 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 19 0 R /Resources <<
+/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
+>> /Rotate 0 /Trans <<
+
+>>
+ /Type /Page
+>>
+endobj
+17 0 obj
+<<
+/PageMode /UseNone /Pages 19 0 R /Type /Catalog
+>>
+endobj
+18 0 obj
+<<
+/Author (\(anonymous\)) /CreationDate (D:20260331161655+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260331161655+02'00') /Producer (ReportLab PDF Library - \(opensource\))
+ /Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
+>>
+endobj
+19 0 obj
+<<
+/Count 10 /Kids [ 5 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 15 0 R 16 0 R ] /Type /Pages
+>>
+endobj
+20 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 367
+>>
+stream
+Gatn`h+kg@(^Api?H%8IB%6fuo1TfT-D3Zi"Vb!G7OJ!uDr5Hj&d[EPD:7mGDu2\33EVZ]>N[Npf&GtdK+$%,AsEO\++'M,`]<[8]68FL--H=*r_=R\_[+I.!I$ZfmDGnMendstream
+endobj
+21 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1444
+>>
+stream
+GauHKD/\E'&H88.0g2KN[)@4f%TSc@3HBE+HGDG^C-V3_<1p+[^7'mG!*eI5A1q;A)"L(*ugMYq2Pi;*LM#n!i'>)(Wa]!^6U>jo3U,j`gh$/mH(mO2U$EeP)8&k4jC>^T%GZ985fg[m+a3S^p*!g[G1E\1O,)-glY(Q$Te5A,hBHC$q)2,E`Nu)4FBVJQpa:X5R)cl`Aa:am2kn/#LPfN8,bc+JjZtM^C0rIC=(bD*9A'S$UI_5$#(3d9$_Nuff^d9a^@Q7q]hKWXEn6q_WOV1B$0HN/0W:FJ7AR4E3('qeP,WU2QB&XIijlk8XH,,T@Y+%q^7XR^F-A>Op`s"diJ26c92qTt\QeR,$$6C/@nLa'@*fNI@5H,N%!j2;*JPuDQ[h$n.e#k]W0a2_Cjd%k/iODQ=ICcBkE@B7Ik5'R&'$NDC''8Q<(L+/QF3'Pj'.4']Y>*WP&pbRT>">J'%MNj0qWr[qm:."+.5Y[c\G2D^"MeU-og(SQ5bp:!\7Q+cW^,7N[C&T':Yl+6"%?fAUUYG?\#jb^5](gD>_Yu\qdtU[q8`73\*AhE9c=2S`L*28R-b[JSPu)Pp[k)ELMTXJeKS6WL'M;a2+I&fXQ6EGdbbe8nXLE20M/Zd:^lhVuO"Ur%(F*Nc0?+Y[hIRjN?T2'endstream
+endobj
+22 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 581
+>>
+stream
+GasalIr!dm&H1LYi`[0YUJkVpp/8oMP2a;[aKes"%1GPmPh)=@V398DP^N4rm3HNeBB^:,+ME-rlcFc#IU9^b"i-3epO`^B%t"C!,7O>Q,4=5Ih?HOhTe'^tKHVWBUD3Zs(DAA:8f,?Jq-tVUE-aBH?TZ?=FnK^I+n,PC0^.+3r$m4):[D9=6(THAdXcbld]RZuWD^Jd)-`p`H+8>+l\c8BT>4eTd[1KFEA_"?dT2T&*@eDlmg2BnO!tC$*f9Lno5X5m'SX2M4-?^c;bNUZd_Hd=^3t3gomsZQ'+7@d2Su82OpcX.?d'Ed@FH03nG4R$88h^mpGj;8hVFi`^9e4rn48UHZ:[[T!'f*:DB7_lrbE8LTm]&qJR$XGSnP8&CSCR&\OOfNddh52263R@$R9$'[JFa9-J%PFfU>4ddp3U4Q@$C(VY7)n.W)H\WKtED@?\Z1T#oF?=;G'TE;kendstream
+endobj
+23 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1164
+>>
+stream
+GatU2qetI`&H4hB`>n.F:?`Z]![LXsP)*)8VRc>ldFAnkFULSDJ'-LnjD4VY2://-HHdM].+--Nt9K@_ITV'@U>gX[=T^H:tm*B(SSiLa-:``a=;'Vj%KZi.Ju:'@+O\$'8'iJdLD2S;\Om"lr@#Kl&q\d-fGNUIOuEs@R&&?A'^tm>Zb&DN3nj&9)V5d#c[sLP2`E$\r&6G'oOMh7q`AmbbX3j+)rFLfCH[Vo22tpSg4EYQa*sDo>f%IccDY8@`mM\*J^5MH9:53.KZ:2>:$;Zo9G<":cI_t$h-9Rs5;A;=O^Q3P?PA`DXhqH]M/Z6(.glO'$AlHtOJmD`>aV=L.od,gY;jchjC`%3VZlZ$)8HtpLt>n`I,L_cO!kW9F2#pC=?>;q%*EH;]]dJ.T\!ObW.4uD6K;I`H!gt*jXV+Cp1,u_;lV5GD_\?-kQnCJS$`0R0j@-r4GB*/NV.>SC?1%i"!eGc%uD=rts``A[0<0EY,L9k2%%qOIargcpQF;'@N?:AS#KnPqt1%bXWA!Y6B4?3!?)(`6;F2Ogk+UW"8J8Sj-@?Z\QJ5IHRt6XO[*TnB(76d[TP`F0@ri[aj1q1sA^A,Ohg6K-lGP4Kc6j0\/3Y[^V5-6SE\sH%tNpkMQatQdt&t^GC*)S>`&E>2VVYla6KP.ji>U"1pBNp5P%__rfUq.!MT3!MR:Qj1a\H8E'[pp'fTg2c(d]62fGTj0@l&AL(7d::$Wh)V[m`"B=h0ZeD"]1PrDPg[DE'P1s'5m)>ODaCA:bq`=2^@6b1YG1bXMDse&hs>8hS!.(QeA!d/~>endstream
+endobj
+24 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1414
+>>
+stream
+Gb!SjD/Z1=&H88.0h(Au;\^uE[Zd5PlfI6(8njP;Xt$jILBY\b"r;)Z@],OlHY4-^S:GZ@l0_1\al[/@Sp=pkFr]_ojlg&9!B?R#rQtYD#Lb[mY3!Om$8/4]T+7BnADafkma)ukkigY;\G\VUd:]L=m#Bd$=T,"4s0r068%.rSi]f4V*`$;I"=uND36Q6j`i-#AWGbT9>^tr,#,lJac7UlcF5R+1f7=>EZQ=T6*aQuu5B?coSFO_Xa`?5YbVam$.EW.e8!:^LBkl"i[dl]Hm(7uF#Qpf;"ME>u*BOOo"LUoJYGmug,#1g$i@$S1,#&]p:A;cNg6g5JLB'sFpAlL"rcrRT[4c/'q)H-2uJV(pZmPk#9<;(dRfqdY!3lnP@U-)CIIn<-=;f"3fLfl`KkbX_9@FSDH.@b^o_9>o&lGh9)7Vu@_#+S)=e+i,9N&f$+1SEff'[^Ft)Ce$T<,fkb"bj'4)i1@mk:=f8U6XcQ><#nZ`<);U&WYu_fYTsl*8k\Ko%\&1AHo86tR6C&<\uB19Jai^BK1b*$&IAl]IXsKU7HV;fYCA6U+dXX/rGlqZ9jI9o^">hpe`smib=TXUOG@j2=V!GuKlaGZOq,>ai`pC4"+hq6?Au^2]ff>$`o"`&Kr.@kS&9-Q9[7Yjd6Xp%g;X2PoH(tC,rd*bb3MVkRkU!9k+MUg/l\m'#OfJg_geH.Z7H)Q^t.`d[8a]8V27c`gEuB`GNtG%c-l#0,=K!JrYh\fUOZ:F?lO7AlErAo)+Xm@[d$C2ZK^@R0"ib2_DCN:h"oTL!QqFoOp0XV4]H6tJm8#_g:_c^[s$eQ8$(I4SsS%kU5ZH^`I#lTr!sbj@JmJ(/W7%cU6-:^_Z16!R@["`sH(j6Ib7d2_Rts-I6*AdJtH"6fRN[/~>endstream
+endobj
+25 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1156
+>>
+stream
+Gb!#Z;/b2I&:XAW3!`XF%$P3^E]0`%$`$FH!a1X3#5U%BJWe:k=,Bm*.X#G!^[F]rYnP5npFou3CeN9++7Hp**YlJH5(WZl$\58]p`BPC`irQ2&:,KNDW#gqpc8]SQKOrc*aXL90V1tN8U,\Z\A8]b$R::f22-9$m(gCZ-Jk8Ll"g&BR06W2W=^]=/;CW,7>^V*fOp*fH2qlUBhKaGPMn`Zi,FIp22qBb#'ruBbX'3H19*$t,j^L5TT#%kC*s.F(MFtc.=>6?H";U3;F7gp)TW[cj1QBSOj"/O5p7Y)ZPpQQ:%e>:>C2c]u5?)*^&9\.O>hrN5'/J%k;h*esTV0<.#CP\^&o>08G3pC8WWff_$i3=!i+.DG'Dd7sI"lc"Bg,[ko$I*ZW_6HVGjg",`;5(.X+LaFKdtBqNS5[4q=Fi8@mB5_:LOsBu[9FnQCg>C]6Kqf9o+'J0AAL!OVVUU!2`[t8cu(+MieS$dt,2]$&-5sLZ'CQ$>d#F\SirVrs><,52u>n^^ZpO`PSY?7$!1>D6CNRp>]H5i\u7%q1.UJ[58FiH;m+SOYZ-6H&i[Y@#1@Ctg$Ui"0FUH9@,ZL?"Nh1Kq0>iFR&ofUQO95rT#i1+tendstream
+endobj
+26 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 1392
+>>
+stream
+Gatm:D/\E'&H88.0sXba;Oq\&&kG!TdJIb4%BkMQIZS`%p$k;(BpM"Cg[HJ:7tN+\Np#;<#RkQIe)&B_WJbbl>1'+.bO$VD,H0+/C)8Zr8HP#V?gXC2PO_.R0Sm,hI3b)MB$D^riVmWLWFj`;pJ]6BU4N]#P6U7X*,b'BFR[ZA8,5W&H)-*8-9BHp!uF"Vd4k!f/nE;mNL*=7*\jq/2DUTX(YSe8W'\NqTZA+#(Pf,=HhB2aijgW7u_nTubJY;'/uS$lFA,&Y)bL6ArC\RN[Hd+-D^UiMHf'n$SDE/:>@8;JJg8pn#6XLH$7>YM-n,29Ka#\d,FlI:'a-Y8l:`'m"H*C4+h0ZiP\*_WK83cmisHp)GVdi2aTRaXFf0K?@u:"fOf[FS+dV(Z?Ua,-'_$Go'J7<5>HDhsDA:s+!]Vj*9Ni&4DJr7%R3ki5lp,7&u:\WZLqNgU[P6#SdNo(.i#=jDjiGT1>FZ,>UE-&>'0%<5hH`/JlKkU$rpA0='<]L'X1a+tDbJOu[/IRF;pk^Jp`NX@XC/ala-m%sVlK@V3SFP`a9ad#OEh;E8h]&IYRe)?rdYXf:MdC+HD*O#2hSn<8")?K.fgej5RQ7iKTfB-YG*:nTr'+4&&rendstream
+endobj
+27 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 317
+>>
+stream
+Garo=;+ne\'SYH9.h4pTo:^$P=<)3B#l:?,@NP9JgG\@IBo6UE8EkU_X`Gn[2fKpqF7+6>3$NkId'F(hQO"Znp_7`V/cl(LKEoWU)*^)j&]>k*$'Nsm\/')NF6mpYe[Dn$3N)E_=^ocd[fu@:J"k`-."\N_6f;QA9RFPl^8u>semLY45e]NjIB.e@GHTB@A;\@IkgA4C@5+O#reXendstream
+endobj
+28 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 757
+>>
+stream
+Gatn#h/8]I&;BTO'M"98ftANj+1LNhj),c7A37#u'a_u+Y_d'JU^P[ET>%p1*'(!f\nXN5L=r!dc#O"/6ZMDV2r+IH'W:+TF9D.>\trQ&!=8(@lfjH\9hR8oa)^*a,L*Yj;rie>AiPN\g!EE2BT_=B&E1>Qnhau:=F!j8P[V2,OOcX&:dgJi%aQ0\SP.=bcB5p-W^lL1?;+qclE0Pfu0.at@e[B#d(#?k,UWhQ%pIt,[;U-Q^lM=pb;`Wg`HT""2(g?@rNA*2\(*#Q4qU:+dh+'^j@4=Z3(fZFtCYo$CUUFh&8cR%3`k(KQB,T`gQ+.KkfGX-J/oK]uB9-Q:$b#*n$c9D%,X>\mZD+qT(g>n%)L$%$Z5qC@\Pl=qR>um)]`.sXBV[IdgTtcZ*-;[T4IsTCiBco[JQ%$d~>endstream
+endobj
+29 0 obj
+<<
+/Filter [ /ASCII85Decode /FlateDecode ] /Length 735
+>>
+stream
+GatUq?#SFN'Sc)P($Dae<12D7E8YW!g9+>_Yr1i"`\\QR#VJlB`XuKb4hAp\4lUd=(B=sN`:mNgp9S2E!ldAFL`8DCJ,pOto`IY2ZX_E(R4,HU$u"9aKXJVkLJ"!M(5iQVSfi>\.t4bj2-'&W/)\sXY:"RBS'W16DR%u?jpfld(:fUWGmWO&HSe!Yn\q^7,9,tl3)+X)OL9\Ke4q70)F6u'<9T`<"h@07-=G%7#Ee3J]`,+=DuV%2c#X*u8rpoH[*G="=:e/[<1!!]N7T0#!_%t902ZnCE#c_^rq:XA+kn=`7`bp`'FQIbe/;jPO\_'SV>edT"Q#9(NuA\a9RlU7<@)-WBVeW8JW7*Fb2$*i(#&:QI%q0<0*LUg0bp1jm;He,FJp#!-5+,8O';n)Fbc73>5[*DSa`ftPSeN-P5TAN`3]\V*Kendstream
+endobj
+xref
+0 30
+0000000000 65535 f
+0000000061 00000 n
+0000000133 00000 n
+0000000240 00000 n
+0000000352 00000 n
+0000000467 00000 n
+0000000672 00000 n
+0000000777 00000 n
+0000000982 00000 n
+0000001187 00000 n
+0000001392 00000 n
+0000001598 00000 n
+0000001804 00000 n
+0000002010 00000 n
+0000002216 00000 n
+0000002294 00000 n
+0000002500 00000 n
+0000002706 00000 n
+0000002776 00000 n
+0000003057 00000 n
+0000003178 00000 n
+0000003636 00000 n
+0000005172 00000 n
+0000005844 00000 n
+0000007100 00000 n
+0000008606 00000 n
+0000009854 00000 n
+0000011338 00000 n
+0000011746 00000 n
+0000012594 00000 n
+trailer
+<<
+/ID
+[]
+% ReportLab generated PDF document -- digest (opensource)
+
+/Info 18 0 R
+/Root 17 0 R
+/Size 30
+>>
+startxref
+13420
+%%EOF
diff --git a/Les09-Live-Coding-Guide.md b/Les09-Live-Coding-Guide.md
new file mode 100644
index 0000000..b0e5183
--- /dev/null
+++ b/Les09-Live-Coding-Guide.md
@@ -0,0 +1,622 @@
+# Les 9 — Supabase Auth
+## Live Coding Guide voor Docent
+
+This is your cheat sheet for the full lesson. Follow the timing in Les09-Docenttekst.md.
+
+---
+
+## DEEL 1A: UITLEG AUTH (09:10–10:00)
+
+### 09:10 | SLIDE 4: Wat is Auth?
+
+**Demo:** Open https://supabase.com/dashboard
+
+Vertel:
+"Authenticatie is: wie ben jij? Login en password, je identiteit bewijzen.
+Autorisatie is: wat mag je doen? Wie mag polls maken? Dit regelen we met RLS policies.
+Supabase Auth beheert alles: signUp, login, sessies, JWT tokens."
+
+**Stap 1:** Dashboard openen, project selecteren
+**Stap 2:** Klik Authentication → Providers → Email
+**Stap 3:** Toon: "Disable Email Confirmations" is AAN
+
+Vertel:
+"Deze checkbox is cruciaal voor testen. Normaal zouden users een confirmation email krijgen voordat ze inloggen. Dat slaan we over voor deze les. In productie zet je dit uit."
+
+---
+
+### 09:20 | SLIDE 5: Auth Functies
+
+**Vertel:**
+"Supabase Auth heeft vier kern functies:"
+
+**Code tonen (copy-paste in terminal of code editor):**
+
+```typescript
+// 1. signUp — Nieuw account
+const { error } = await supabase.auth.signUp({
+ email: "user@example.com",
+ password: "secure123"
+});
+if (error) console.error(error.message);
+
+// 2. signInWithPassword — Inloggen
+const { error } = await supabase.auth.signInWithPassword({
+ email: "user@example.com",
+ password: "secure123"
+});
+if (error) console.error(error.message);
+
+// 3. signOut — Uitloggen
+await supabase.auth.signOut();
+
+// 4. getUser — Wie is ingelogd?
+const { data: { user } } = await supabase.auth.getUser();
+console.log(user.email); // "user@example.com"
+console.log(user.id); // "abc-123-def"
+```
+
+Vertel:
+"Diese vier functies zijn alles wat je nodig hebt. Error handling: altijd checken of `error` null is."
+
+---
+
+### 09:30 | SLIDE 6: Server vs Browser Client
+
+**Vertel:**
+"Supabase Auth werkt op twee plaatsen:
+1. **Server (Node.js)** — Secure, via cookies
+2. **Browser (React)** — Less secure, via localStorage
+
+We gebruiken @supabase/ssr. Dit switcht automatisch."
+
+**Show left code block (Server Client):**
+
+```typescript
+// lib/supabase-server.ts
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createSupabaseServerClient() {
+ const cookieStore = await cookies();
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return cookieStore.getAll(); },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch { }
+ },
+ },
+ }
+ );
+}
+```
+
+Vertel:
+"Server client gebruikt cookies. Cookies kunnen beveiligd worden (httpOnly, secure-only). Dit is safer."
+
+**Show right code block (Browser Client):**
+
+```typescript
+// lib/supabase-browser.ts
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createSupabaseBrowserClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+```
+
+Vertel:
+"Browser client werkt in React. localStorage is minder secure (scripts kunnen het uitlezen), maar nodig voor login forms."
+
+**Key difference:**
+"Server component (Navbar) → Server client (getUser)
+Client component (LoginForm) → Browser client (signUp, signIn, signOut)"
+
+---
+
+## DEEL 1B: SAMEN CODEREN (10:00–10:15)
+
+Students volgen mee terwijl je dit live codeert (of ze kopieren uit les09-live-coding-guide.md).
+
+### Stap 1: npm install
+
+```bash
+npm install @supabase/ssr
+```
+
+Wacht tot dit klaar is.
+
+### Stap 2: lib/supabase-server.ts
+
+Maak dit bestand aan:
+
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createSupabaseServerClient() {
+ const cookieStore = await cookies();
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return cookieStore.getAll(); },
+ setAll(cookiesToSet) {
+ try {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ cookieStore.set(name, value, options)
+ );
+ } catch { }
+ },
+ },
+ }
+ );
+}
+```
+
+### Stap 3: lib/supabase-browser.ts
+
+```typescript
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createSupabaseBrowserClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+```
+
+### Stap 4: middleware.ts (ROOT of project, naast app/)
+
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { NextResponse, type NextRequest } from "next/server";
+
+export async function middleware(request: NextRequest) {
+ let supabaseResponse = NextResponse.next({ request });
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return request.cookies.getAll(); },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
+ supabaseResponse = NextResponse.next({ request });
+ cookiesToSet.forEach(({ name, value, options }) =>
+ supabaseResponse.cookies.set(name, value, options)
+ );
+ },
+ },
+ }
+ );
+ await supabase.auth.getUser();
+ return supabaseResponse;
+}
+
+export const config = {
+ matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"],
+};
+```
+
+Vertel:
+"Middleware runt op elke request. `await supabase.auth.getUser()` refresht de session token. Dit zorgt dat je niet uitgelogd wordt als je token expired."
+
+### Stap 5: app/auth/callback/route.ts
+
+```typescript
+import { NextResponse } from "next/server";
+import { createSupabaseServerClient } from "@/lib/supabase-server";
+
+export async function GET(request: Request) {
+ const { searchParams, origin } = new URL(request.url);
+ const code = searchParams.get("code");
+ if (code) {
+ const supabase = await createSupabaseServerClient();
+ await supabase.auth.exchangeCodeForSession(code);
+ }
+ return NextResponse.redirect(origin);
+}
+```
+
+Vertel:
+"Dit is voor OAuth (Google, GitHub). Supabase stuur je hier naartoe na login. De `code` wordt ge-exchanged voor een session. Voor nu: boilerplate, niet essentieel."
+
+### Test middleware
+
+Vertel:
+"Test of middleware werkt: open http://localhost:3000. Je zou geen errors moeten zien. In browser dev tools → Application → Cookies → zoek naar `sb-*`. Die cookies beteken dat middleware goed werkt."
+
+---
+
+## PAUZE (10:15–10:30)
+
+---
+
+## DEEL 2: ZELF DOEN (10:30–11:30)
+
+Students bouwen nu zelf. Jij loopt rond, helpt, en toont code op beamer als studenten stuck zijn.
+
+### Zelf Doen Checklist (wat moet elke student doen):
+
+- [ ] app/signup/page.tsx
+- [ ] app/login/page.tsx
+- [ ] components/LogoutButton.tsx
+- [ ] components/Navbar.tsx
+- [ ] app/layout.tsx updated
+- [ ] Test signup → login → poll maken → logout
+
+---
+
+### 1. app/signup/page.tsx
+
+Dit is een 'use client' component met form. Students schrijven dit zelf, maar hier is de reference:
+
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import Link from "next/link";
+
+export default function SignUp() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+
+ const handleSignUp = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setMessage("");
+ const { error } = await supabase.auth.signUp({ email, password });
+ if (error) { setMessage(error.message); }
+ else { setMessage("Account aangemaakt!"); router.push("/login"); }
+ setLoading(false);
+ };
+
+ return (
+
+
Registreren
+
+ {message &&
{message}
}
+
+ Al een account? Inloggen
+
+
+ );
+}
+```
+
+**Students moeten begrijpen:**
+- `'use client'` = React component
+- `createSupabaseBrowserClient()` = browser auth
+- `supabase.auth.signUp({ email, password })` = nieuwe user
+- `if (error)` = error handling
+- `router.push("/login")` = redirect na success
+
+---
+
+### 2. app/login/page.tsx
+
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import Link from "next/link";
+
+export default function Login() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+
+ const handleLogin = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setMessage("");
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
+ if (error) { setMessage(error.message); }
+ else { router.push("/"); router.refresh(); }
+ setLoading(false);
+ };
+
+ return (
+
+
Inloggen
+
+ {message &&
{message}
}
+
+ Nog geen account? Registreren
+
+
+ );
+}
+```
+
+**Key difference van signup:**
+- `signInWithPassword()` i.p.v. `signUp()`
+- `router.refresh()` om Navbar te update
+- Error styling: `text-red-600`
+
+---
+
+### 3. components/LogoutButton.tsx
+
+```typescript
+'use client'
+import { createSupabaseBrowserClient } from "@/lib/supabase-browser";
+import { useRouter } from "next/navigation";
+
+export function LogoutButton() {
+ const router = useRouter();
+ const supabase = createSupabaseBrowserClient();
+ const handleLogout = async () => {
+ await supabase.auth.signOut();
+ router.push("/");
+ router.refresh();
+ };
+ return (
+
+ );
+}
+```
+
+**Dit is een klein client component. Belangrijk:**
+- `'use client'` (event handler)
+- `signOut()` — geen params
+- `router.refresh()` — update Navbar
+
+---
+
+### 4. components/Navbar.tsx
+
+Dit is het interessantste component. Server Component met `async`:
+
+```typescript
+import Link from "next/link";
+import { createSupabaseServerClient } from "@/lib/supabase-server";
+import { LogoutButton } from "./LogoutButton";
+
+export async function Navbar() {
+ const supabase = await createSupabaseServerClient();
+ const { data: { user } } = await supabase.auth.getUser();
+ return (
+
+ );
+}
+```
+
+**Vertel:**
+"Navbar is een **Server Component** (geen 'use client'). Dit betekent `async` is ok. We callen `getUser()` direct — geen hooks nodig!
+
+`getUser()` gebruikt server client + cookies. Dit is beveiligd en efficient."
+
+**Logica:**
+- Als `user` bestaat: toon email + LogoutButton
+- Anders: toon Inloggen + Registreren links
+
+---
+
+### 5. app/layout.tsx (update)
+
+Voeg Navbar toe:
+
+```typescript
+import type { Metadata } from "next";
+import { Geist, Geist_Mono } from "next/font/google";
+import "./globals.css";
+import { Navbar } from "@/components/Navbar";
+
+const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
+const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
+
+export const metadata: Metadata = { title: "QuickPoll", description: "Stem op je favoriete opties" };
+
+export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
+```
+
+---
+
+### TROUBLESHOOTING (10:30–11:30)
+
+Als studenten stuck zijn, gebruik deze tabel:
+
+| Symptoom | Oorzaak | Oplossing |
+|----------|---------|----------|
+| "Module not found: @supabase/ssr" | npm install niet gedaan | `npm install @supabase/ssr` |
+| Navbar toont altijd "Inloggen" (ook na login) | getUser() returns null | Check: cookies middleware working? Browser dev tools → Cookies (zoek sb-*) |
+| Login werkt, maar redirect loopt vast | router.refresh() niet in handleLogin | Voeg `router.refresh()` toe na success |
+| "Invalid PKCE flow" | Browser client not configured | Check .env: NEXT_PUBLIC_SUPABASE_URL en ANON_KEY kloppen |
+| Logout knop werkt niet | signOut() niet awaited | Zorg: `await supabase.auth.signOut()` |
+| Navbar.tsx error: "cannot use async in component" | Navbar is client component | Zorg: geen `'use client'` aan top van Navbar! |
+
+---
+
+### 11:00 | CHECK-IN: NAVBAR DEMO
+
+Toon op beamer je eigen Navbar. Vertel:
+
+"Navbar is een **Server Component**. Dit is uniek voor Next.js. In React kan je geen `async` functions gebruiken als components.
+
+Hier kan het wel, omdat Next.js bij build-time Server Components render. Cookies zijn secure. getUser() is beveiligd. Dit is beter dan client-side auth check."
+
+Toon: `const { data: { user } } = await supabase.auth.getUser();`
+
+"Dat een lijn doet alles: leest cookies → vraagt Supabase → geeft user object."
+
+---
+
+### 11:15 | RLS UPDATE
+
+Voer in Supabase dashboard SQL uit:
+
+**SQL Editor → New Query:**
+
+```sql
+-- Enable RLS on polls table
+ALTER TABLE polls ENABLE ROW LEVEL SECURITY;
+
+-- Anyone can read polls
+CREATE POLICY "polls_select_all" ON polls
+ FOR SELECT USING (true);
+
+-- Only authenticated users can create polls
+CREATE POLICY "polls_insert_authenticated" ON polls
+ FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
+
+-- Only the creator can update their own poll
+CREATE POLICY "polls_update_owner" ON polls
+ FOR UPDATE USING (auth.uid() = created_by);
+```
+
+Vertel:
+"RLS = Row Level Security. Dit enforces wie wat kan doen op database level.
+- Iedereen (anoniem) kan polls zien (SELECT)
+- Alleen ingelogde users (auth.uid() NOT NULL) mogen polls maken (INSERT)
+- Alleen de maker mag hun eigen poll updaten (UPDATE)
+
+auth.uid() is de user ID van Supabase. NULL als je niet ingelogd bent."
+
+**Test:**
+1. Open http://localhost:3000/create
+2. Niet ingelogd → knop grijs / form gedeactiveerd
+3. Inloggen
+4. Poll aanmaken → werkt!
+5. Uitloggen
+6. /create opnieuw → weer grijs (RLS blokkeert INSERT)
+
+---
+
+### 11:30–11:45 | VRAGEN & DEBUGGING
+
+Loopround. Antwoord vragen:
+
+**Q: Hoe debug ik auth?**
+A: Supabase dashboard → Auth → Users. Daar zie je alle users. Of: Browser dev tools → Application → Cookies (zoek `sb-*` prefix).
+
+**Q: Hoe reset ik mijn test account?**
+A: Dashboard → Auth → Users → klik user → delete → registreer opnieuw.
+
+**Q: Waarom zie ik geen email na login?**
+A: Middleware werkt niet. Zorg middleware.ts in root project staat. Check: `matcher` is correct.
+
+**Q: Kan ik multiple providers (Google, GitHub) toevoegen?**
+A: Ja, later. Dashboard → Auth → Providers. Voor nu: email-password is genoeg.
+
+---
+
+## HUISWERK & AFSLUITING (11:45–12:00)
+
+**Huiswerk (slides 9):**
+
+1. **Profiel pagina (Les 10)**
+ ```typescript
+ // app/profile/page.tsx
+ import { createSupabaseServerClient } from "@/lib/supabase-server";
+
+ export default async function ProfilePage() {
+ const supabase = await createSupabaseServerClient();
+ const { data: { user } } = await supabase.auth.getUser();
+ return {user?.email}
;
+ }
+ ```
+
+2. **Maker tonen bij poll (Les 10)**
+ - Voeg `created_by uuid` kolom toe polls tabel
+ - Update INSERT in /create om `created_by: user.id` toe te voegen
+ - Toon "Gemaakt door: [email]" op homepage
+
+3. **Google OAuth (Bonus)**
+ - Supabase dashboard → Auth → Providers → Google
+ - Copy Client ID en Secret van Google Cloud
+ - Voeg button toe: `signInWithOAuth({ provider: 'google' })`
+
+**Afsluiting (slide 10):**
+
+"Volgende les: **Deployment**. We zetten je app live op Vercel. Dan kunnen je vrienden echt je polls gebruiken!
+
+Daarna: Google OAuth, profiel updaten, meer security features.
+
+Vandaag hebben we de kern van auth gebouwd. Goed gedaan!"
+
+---
+
+## DOCS
+
+Supabase Auth docs: https://supabase.com/docs/guides/auth/server-side/nextjs
+
+Next.js Server Components: https://nextjs.org/docs/app/building-your-application/rendering/server-components
diff --git a/Les09-Slide-Overzicht.md b/Les09-Slide-Overzicht.md
new file mode 100644
index 0000000..4596f76
--- /dev/null
+++ b/Les09-Slide-Overzicht.md
@@ -0,0 +1,270 @@
+# Les 9 — Supabase Auth
+## Slide Overzicht
+
+---
+
+## Slide 1: Title
+### Les 9 — Supabase Auth
+
+**Visual:** Large centered title with QuickPoll icon
+- Background: CREAM
+- "Les 9" in BLUE
+- "Supabase Auth" in BLACK
+- Subtitle: "signUp, signIn, signOut, Navbar, RLS"
+
+---
+
+## Slide 2: Terugblik (Recap)
+### Waar staan we?
+
+**Content:**
+- Supabase project aangemaakt en gekoppeld
+- /create pagina gebouwd
+- Server Component + VoteForm patroon
+- Polls werkend in database
+- Real-time votes
+- "Nu: beveiligde login"
+
+**Visual:**
+- Left: screenshot van huidige app
+- Right: checkmarks of badges
+
+---
+
+## Slide 3: Planning
+### Vandaag — 120 minuten
+
+| Tijd | Onderwerp | Duur |
+|------|-----------|------|
+| 09:00–09:10 | Welkom + Terugblik | 10 min |
+| 09:10–10:00 | Uitleg Auth | 50 min |
+| 10:00–10:15 | Samen Middleware bouwen | 15 min |
+| 10:15–10:30 | **Pauze** | 15 min |
+| 10:30–11:30 | Zelf Doen (signup, login, Navbar) | 60 min |
+| 11:30–11:45 | Vragen & Debugging | 15 min |
+| 11:45–12:00 | Huiswerk + Afsluiting | 15 min |
+
+**Visual:** Timeline with YELLOW background, icons per blok
+
+---
+
+## Slide 4: Wat is Auth?
+### Authenticatie vs Autorisatie
+
+**Authenticatie (WHO):**
+- Wie ben jij?
+- Email + password
+- Supabase verifies en geeft JWT token
+- User object: email, id, created_at
+
+**Autorisatie (WHAT):**
+- Wat mag je doen?
+- Wie mag polls maken?
+- Later: RLS policies
+
+**Features van Supabase Auth:**
+- Email/password signup & signin
+- Session management (cookies)
+- JWT tokens
+- Password reset
+- Multi-factor auth (later)
+- OAuth (Google, GitHub, etc.)
+
+**Visual:**
+- Left: "Authentication" icon (person + key)
+- Right: "Authorization" icon (person + checkmark)
+- Supabase logo
+
+---
+
+## Slide 5: Auth Functies
+### Vier Core Operations
+
+**signUp**
+```typescript
+const { error } = await supabase.auth.signUp({
+ email: "user@example.com",
+ password: "secure123"
+});
+```
+→ Account aanmaken
+
+**signInWithPassword**
+```typescript
+const { error } = await supabase.auth.signInWithPassword({
+ email: "user@example.com",
+ password: "secure123"
+});
+```
+→ Inloggen
+
+**signOut**
+```typescript
+await supabase.auth.signOut();
+```
+→ Uitloggen
+
+**getUser**
+```typescript
+const { data: { user } } = await supabase.auth.getUser();
+// user.email, user.id, user.email_confirmed_at
+```
+→ Huidige user
+
+**Visual:** Code blocks in BLUE boxes, icons above each
+
+---
+
+## Slide 6: Server vs Browser Client
+### Two Clients, One Auth
+
+**Server Client** (@supabase/ssr)
+```typescript
+import { createServerClient } from "@supabase/ssr";
+import { cookies } from "next/headers";
+
+export async function createSupabaseServerClient() {
+ const cookieStore = await cookies();
+ return createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() { return cookieStore.getAll(); },
+ setAll(cookiesToSet) { /* ... */ },
+ },
+ }
+ );
+}
+```
+
+**Use in:**
+- Middleware (refresh token)
+- Server Components (Navbar, getUser)
+- API routes
+
+**Browser Client** (@supabase/ssr)
+```typescript
+import { createBrowserClient } from "@supabase/ssr";
+
+export function createSupabaseBrowserClient() {
+ return createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+ );
+}
+```
+
+**Use in:**
+- Client Components ('use client')
+- Login forms
+- Logout buttons
+
+**Visual:** Two side-by-side code blocks
+- Left: Server (BLUE bg), lock icon
+- Right: Browser (PINK bg), web icon
+
+**Key difference:**
+- Server: Cookies (secure, secure-only, httpOnly)
+- Browser: localStorage (accessible, but less safe)
+
+---
+
+## Slide 7: Pauze
+### Pauze!
+
+**Visual:** Relaxed illustration, "15 minuten", clock
+
+---
+
+## Slide 8: Zelf Doen — Auth Bouwen
+### Nu jij — 60 minuten
+
+**To-Do:**
+- [ ] app/signup/page.tsx (form)
+- [ ] app/login/page.tsx (form)
+- [ ] components/LogoutButton.tsx
+- [ ] components/Navbar.tsx (Server Component + getUser)
+- [ ] app/layout.tsx (add ``)
+- [ ] Update RLS policies (authenticated only!)
+
+**Reference code beschikbaar** (docent toont op beamer)
+
+**Process:**
+1. Start simpel: form met email + password inputs
+2. Voeg supabase.auth.signUp / signInWithPassword toe
+3. Test in browser
+4. Navbar: toon email of login link
+5. RLS: polls INSERT nur voor authenticated users
+
+**Expected result:**
+- Registreren → inloggen → poll maken → uitloggen
+- Na logout: kan geen poll meer maken (RLS!)
+
+**Visual:** Big BLUE background, "Bouw Auth" header, checklist
+
+---
+
+## Slide 9: Huiswerk
+### Volgende Stap
+
+**Verplicht (Les 10):**
+1. Profiel pagina (app/profile/page.tsx)
+ - Toon user.email, user.id
+ - Later: password update form
+
+2. Maker tonen bij poll
+ - Voeg `created_by` kolom toe polls tabel
+ - Toon "Gemaakt door: [email]" bij elke poll
+ - RLS: alleen maker mag aanpassen (UPDATE)
+
+**Bonus (optioneel):**
+1. Google OAuth signup
+ - Supabase dashboard → Auth → Providers → Google
+ - Voeg "Sign in with Google" knop toe
+
+2. Password reset
+ - Email link naar reset form
+ - supabase.auth.resetPasswordForEmail()
+
+**Visual:** Checklist, bonus items in PINK
+
+---
+
+## Slide 10: Afsluiting
+### Volgende Les — Deployment
+
+**Wat hebben we gedaan vandaag:**
+- Auth concepten: authenticatie vs autorisatie
+- Supabase Auth functies: signUp, signIn, signOut, getUser
+- Server vs browser client
+- Middleware voor session refresh
+- Navbar met authenticated user
+- RLS policies
+
+**Volgende keer:**
+- Vercel deployment
+- Google OAuth
+- Profiel pagina
+- Meer security!
+
+**Vragen? Feedback?**
+
+**Visual:** Vercel logo, rocket icon, "Deployment!" in YELLOW
+
+---
+
+## Slide Summary
+
+| # | Title | Duration | Key Content |
+|---|-------|----------|-------------|
+| 1 | Title | Opening | Les 9 — Supabase Auth |
+| 2 | Recap | 09:10 | Where we are |
+| 3 | Plan | 09:05 | 120-min schedule |
+| 4 | Auth Concepts | 09:10 | Auth vs AuthN, Supabase features |
+| 5 | Functions | 09:20 | signUp, signIn, signOut, getUser |
+| 6 | Clients | 09:30 | Server vs Browser (@supabase/ssr) |
+| 7 | Break | 10:15 | 15 min pauze |
+| 8 | Build | 10:30 | Students implement auth |
+| 9 | Homework | 11:45 | Profile, maker, Google OAuth |
+| 10 | Closing | 11:55 | Next: Deployment |
diff --git a/Les09-Slides.pptx b/Les09-Slides.pptx
new file mode 100644
index 0000000..0934b2d
Binary files /dev/null and b/Les09-Slides.pptx differ