fix: lessons
This commit is contained in:
229
Les09-Supabase-Auth/Les09-Lesstof.md
Normal file
229
Les09-Supabase-Auth/Les09-Lesstof.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# 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".
|
||||
Reference in New Issue
Block a user