# 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

setEmail(e.target.value)} className="w-full p-2 border rounded" required />
setPassword(e.target.value)} className="w-full p-2 border rounded" minLength={6} required />
{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

setEmail(e.target.value)} className="w-full p-2 border rounded" required />
setPassword(e.target.value)} className="w-full p-2 border rounded" required />
{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