Files
novi-lessons/Les09-Supabase-Auth/Les09-Lesstof.md
2026-06-07 15:15:11 +02:00

230 lines
7.2 KiB
Markdown

# Les 9 — Supabase Auth: Eerste Login Flow
## Lesstof
Eerste echte authenticatie voor QuickPoll. Aan het einde van deze les heeft je app signup, login, logout én een werkende Navbar die de sessie-status toont. We sluiten af met een eerste blik op Row Level Security (RLS) als opmaat voor Les 10.
---
## 1. Waarom Supabase Auth?
Je hebt drie opties als je een webapp wil beveiligen:
1. **Zelf bouwen** — password-hashing, sessie-cookies, e-mailverificatie, OAuth-callbacks, password-reset, rate-limiting. Een fulltime baan.
2. **Auth0 / Clerk / Cognito** — externe service, prima maar duur en je data ligt elders.
3. **Supabase Auth** — zit naast je database, gratis voor onze schaal, werkt out-of-the-box met RLS.
Voor deze cursus kiezen we Supabase Auth omdat het een directe lijn heeft naar je Postgres-database. Dat maakt RLS (autorisatie op database-niveau) mogelijk.
### Authenticatie vs. Autorisatie
- **Authenticatie:** wie ben je? (login + wachtwoord, magic link, OAuth)
- **Autorisatie:** wat mag je? (RLS-policies, role checks)
Vandaag focussen we op authenticatie. Autorisatie (RLS) gaan we in detail behandelen in Les 10.
---
## 2. Supabase Auth in vogelvlucht
Supabase Auth ondersteunt veel methodes. De drie meest gebruikte:
| Methode | UX | Geschikt voor |
|---------|----|----|
| E-mail + wachtwoord | Klassiek | Apps met geregistreerde gebruikers |
| Magic link | Klik op link in e-mail | Lage drempel, geen wachtwoord onthouden |
| OAuth (Google/GitHub) | Eén klik | Snelle signup voor consumer apps |
Vandaag gebruiken we e-mail + wachtwoord. Magic link en OAuth zijn beide twee config-regels extra.
### De vier kern-functies
```typescript
// 1. signUp — nieuw account
await supabase.auth.signUp({ email, password });
// 2. signInWithPassword — inloggen
await supabase.auth.signInWithPassword({ email, password });
// 3. signOut — uitloggen
await supabase.auth.signOut();
// 4. getUser — wie is ingelogd?
const { data: { user } } = await supabase.auth.getUser();
```
Dat is het. Geen complexer API. De rest doet Supabase voor je: cookies, JWT-refresh, e-mailverificatie.
---
## 3. Sessies en `@supabase/ssr`
Een sessie is een geldig JWT-token. Supabase bewaart dit in cookies. Het pakket `@supabase/ssr` zorgt dat zowel je server-components als je middleware automatisch toegang hebben tot deze cookie.
### Server-client setup
```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 { /* server component, geen mutation */ }
},
},
}
);
}
```
Belangrijk: in een server component **mag** je geen cookies muteren. Daarom de `try/catch`. De middleware doet de cookie-refresh.
---
## 4. Server actions voor signUp en signIn
```typescript
// app/signup/actions.ts
"use server";
import { createSupabaseServerClient } from "@/lib/supabase-server";
import { redirect } from "next/navigation";
export async function signUpAction(formData: FormData) {
const supabase = await createSupabaseServerClient();
const { error } = await supabase.auth.signUp({
email: formData.get("email") as string,
password: formData.get("password") as string,
});
if (error) return { error: error.message };
redirect("/");
}
```
Dezelfde structuur voor `signInAction` met `signInWithPassword`. Server actions houden de wachtwoord-flow uit de browser.
---
## 5. Navbar met sessie-status
De Navbar is een server component. Hij vraagt de huidige user op en toont "Inloggen" of "Uitloggen" op basis daarvan.
```tsx
// components/Navbar.tsx
import { createSupabaseServerClient } from "@/lib/supabase-server";
import Link from "next/link";
import { signOutAction } from "@/app/signout/actions";
export async function Navbar() {
const supabase = await createSupabaseServerClient();
const { data: { user } } = await supabase.auth.getUser();
return (
<nav className="flex gap-4 p-4 border-b">
<Link href="/">QuickPoll</Link>
<div className="ml-auto">
{user ? (
<form action={signOutAction}>
<span className="mr-3">{user.email}</span>
<button type="submit">Uitloggen</button>
</form>
) : (
<Link href="/login">Inloggen</Link>
)}
</div>
</nav>
);
}
```
---
## 6. Middleware voor sessie-refresh
Zonder middleware verloopt je sessie na 1 uur. Met deze middleware blijft je token automatisch actueel.
```typescript
// middleware.ts
import { type NextRequest } from "next/server";
import { updateSession } from "@/lib/supabase-middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
```
De `updateSession`-helper (template uit Supabase docs) leest de cookie, verlengt het token en blokkeert optioneel routes voor anonieme bezoekers.
---
## 7. Eerste blik op Row Level Security (RLS)
Tot nu toe was je `polls`-tabel "open": iedereen kan alles lezen en schrijven. Dat moet beter. RLS = autorisatie op database-niveau, ingebakken in Postgres.
```sql
-- Zet RLS aan op polls
alter table polls enable row level security;
-- Iedereen mag alle polls lezen
create policy "polls zijn publiek leesbaar"
on polls for select
using (true);
-- Alleen ingelogde gebruikers mogen polls maken
create policy "ingelogde gebruikers mogen polls maken"
on polls for insert
with check (auth.uid() is not null);
```
Belangrijk: `auth.uid()` is een Postgres-functie die Supabase invult op basis van het JWT van de huidige sessie. Geen JWT? Dan is `auth.uid()` `null`.
In Les 10 gaan we dit veel verder uitbouwen.
---
## 8. Checklist einde les
Aan het einde van deze les heb je:
- `@supabase/ssr` geïnstalleerd en geconfigureerd
- Server-client helper (`lib/supabase-server.ts`)
- `/signup` route met server action
- `/login` route met server action
- `/signout` server action
- Navbar met sessie-status
- Middleware die routes beschermt
- RLS aan voor `polls` (twee policies)
Als één van deze ontbreekt: pak de Live-Coding-Guide erbij en loop het stappenplan door.
---
## 9. Veelgemaakte fouten
| Fout | Oplossing |
|------|-----------|
| `auth.uid() is null` ondanks login | Middleware niet geconfigureerd — sessie wordt niet ververst |
| "Email not confirmed" | Zet "Disable Email Confirmations" aan in dashboard voor lokaal |
| Navbar updatet niet na login | Vergeten `revalidatePath("/")` of `redirect()` aan eind van action |
| `cookies()` errors in server component | Niet proberen te `set()` in een server component zelf |
---
## 10. Volgende les
Les 10 gaat veel dieper op RLS in: meerdere policies, `auth.uid()` overal, en het verschil tussen `using` (read) en `with check` (write). We voegen ook eigenaarschap toe: "alleen ik mag mijn polls bewerken".