230 lines
7.2 KiB
Markdown
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".
|