8.8 KiB
Les 18 — Huiswerk
Multi-user Tasks app met RLS + social login + AUTH.md
Vak: AI-Assisted Development
Deadline: Voor eindopdracht-inlevering (4 weken)
Inleveren: GitHub repo + AUTH.md in root + Vercel productie URL
Doel
Bouw je auth-demo uit naar productie-niveau: tasks per user met RLS, GitHub social login, en deploy. Eindopdracht-ready.
Onderdeel A — Tasks tabel + RLS (verplicht)
A1 — Schema
In Supabase SQL Editor:
create table tasks (
id bigserial primary key,
user_id uuid not null references auth.users(id) default auth.uid(),
text text not null,
done boolean default false,
created_at timestamp default now()
);
alter table tasks enable row level security;
create policy "select own tasks" on tasks
for select using (auth.uid() = user_id);
create policy "insert own tasks" on tasks
for insert with check (auth.uid() = user_id);
create policy "update own tasks" on tasks
for update using (auth.uid() = user_id);
create policy "delete own tasks" on tasks
for delete using (auth.uid() = user_id);
A2 — Tasks UI
app/page.tsx — extend met tasks lijst + form:
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
async function addTask(formData: FormData) {
"use server";
const supabase = await createClient();
await supabase.from("tasks").insert({ text: formData.get("text") as string });
revalidatePath("/");
}
async function toggleTask(id: number, done: boolean) {
"use server";
const supabase = await createClient();
await supabase.from("tasks").update({ done: !done }).eq("id", id);
revalidatePath("/");
}
async function deleteTask(id: number) {
"use server";
const supabase = await createClient();
await supabase.from("tasks").delete().eq("id", id);
revalidatePath("/");
}
export default async function Home() {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect("/login");
const { data: tasks } = await supabase
.from("tasks")
.select("*")
.order("created_at", { ascending: false });
return (
<main className="p-8 max-w-2xl mx-auto">
<h1 className="text-2xl mb-4">Tasks van {user.email}</h1>
<form action={addTask} className="flex gap-2 mb-6">
<input name="text" className="border p-2 flex-1" placeholder="Nieuwe taak..." />
<button className="bg-blue-600 text-white px-4 py-2 rounded">+</button>
</form>
<ul className="space-y-2">
{tasks?.map((t) => (
<li key={t.id} className="flex items-center gap-2 p-2 border rounded">
<form action={toggleTask.bind(null, t.id, t.done)}>
<button className={t.done ? "line-through" : ""}>{t.text}</button>
</form>
<form action={deleteTask.bind(null, t.id)} className="ml-auto">
<button className="text-red-500">×</button>
</form>
</li>
))}
</ul>
</main>
);
}
Eisen
taskstabel met RLS aan- 4 policies: select / insert / update / delete
- UI: add, toggle, delete tasks werkt
- In incognito met ander account: andere tasks zichtbaar (geen vermenging)
Onderdeel B — Social login (GitHub) (verplicht)
B1 — GitHub OAuth App
- github.com → Settings → Developer settings → OAuth Apps → New
- Application name: jouw app
- Homepage URL:
http://localhost:3000(dev) ofhttps://yourapp.vercel.app(prod) - Callback URL:
https://your-project.supabase.co/auth/v1/callback - Save, generate Client Secret
B2 — Configure in Supabase
- Dashboard → Auth → Providers → GitHub
- Enable + plak Client ID + Secret
B3 — Button toevoegen
In app/login/page.tsx:
async function signInWithGitHub() {
const supabase = createClient();
await supabase.auth.signInWithOAuth({
provider: "github",
options: { redirectTo: `${location.origin}/auth/callback` },
});
}
// In render:
<button onClick={signInWithGitHub} className="border px-4 py-2 mt-3 rounded">
Of: Login met GitHub
</button>
Eisen
- GitHub OAuth App aangemaakt
- Supabase configured
- Klik op GitHub button → redirect → ingelogd
- Tasks werken met GitHub-account
Onderdeel C — Deploy naar Vercel (verplicht)
Combineert Les 14/15:
- Push naar GitHub
- Vercel: import repo
- Add env vars:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY - Deploy
- Update Supabase Auth → URL Configuration:
- Site URL:
https://yourapp.vercel.app - Redirect URLs:
https://yourapp.vercel.app/**,http://localhost:3000/**
- Site URL:
- Update GitHub OAuth App callback URL to productie
Eisen
- App live op Vercel
- Magic link werkt op productie URL
- GitHub login werkt op productie URL
- Tasks per user werken op productie
Onderdeel D — AUTH.md documentatie (verplicht)
In repo-root.
Sectie 1 — Auth methoden
Welke auth methoden ondersteund:
- Magic link (default)
- GitHub OAuth
- (eventuele extra's)
Sectie 2 — RLS policies
Lijst je policies + waarom:
**Table: tasks**
- `select own tasks` — users zien alleen eigen tasks
- `insert own tasks` — voorkomt inserten op andermans naam
- `update own tasks` — alleen eigen toggle
- `delete own tasks` — alleen eigen delete
Sectie 3 — Test van multi-user isolation
Bewijs dat RLS werkt:
- Screenshot van Account A's tasks
- Screenshot van Account B's tasks (incognito)
- Bewijs: account A's tasks staan NIET in account B's lijst
Sectie 4 — Deployment checklist
Wat je moest aanpassen voor productie:
- Site URL in Supabase
- Redirect URLs in Supabase
- GitHub OAuth callback URL
- Env vars in Vercel
Sectie 5 — Eindopdracht-architectuur (~250 woorden)
Concrete plan voor je eigen eindopdracht:
- App naam + doel
- User-flows (signup, login, kernfeature)
- Welke tabellen + RLS policies
- AI feature(s) en hoe ze user-context gebruiken
- Externe APIs (uit Les 17)
- Tijd-plan (4-6 weken)
Vorm
- Max 800 woorden
- Screenshots zijn waardevol
- Concrete plan, geen vaagheid
Bonus (optioneel)
Bonus 1 — Email + password naast magic link
Voeg klassieke signup/login toe. Combine met magic link.
Bonus 2 — Profile pagina
/profile — user kan naam, avatar URL, etc. instellen. Aparte profiles tabel met RLS.
Bonus 3 — Team feature
Multi-user binnen team. Aparte tabel teams, junction team_members. RLS policies die team-leden toelaten elkaars data te zien.
Bonus 4 — AI integration met user context
Voeg een AI chat toe waar de user-tasks deel zijn van de context. AI weet wat jouw tasks zijn (door RLS), kan helpen prioriteren.
Inleveren
- GitHub repo URL in Brightspace
- Vercel productie URL in Brightspace
AUTH.mdin repo-root- Test-account credentials voor docent (email of GitHub)
- 2-3 screenshots van multi-user test
Beoordeling
| Criterium | Punten |
|---|---|
| A — Tasks + RLS werkt (4 policies) | 3 |
| B — GitHub OAuth werkt | 2 |
| C — Deployed op Vercel + auth werkt op prod | 2 |
| D — AUTH.md compleet (5 secties) | 2 |
| Multi-user isolation bewezen | 1 |
| Totaal | 10 |
Voldoende = 6+. Bonus telt mee.
Tijd-indicatie
| Onderdeel | Tijd |
|---|---|
| A — Tasks + RLS | 50 min |
| B — GitHub OAuth | 25 min |
| C — Deploy | 20 min |
| D — AUTH.md | 35 min |
| Totaal | ~2 uur 10 min |
Veelvoorkomende valkuilen
| Probleem | Oplossing |
|---|---|
| Tasks van andere users zichtbaar | RLS niet aan, of policy using (true) |
| Insert faalt met "row violates" | RLS check faalt — check with check (auth.uid() = user_id) + default |
| OAuth redirect 404 | Site URL niet bijgewerkt voor productie |
| Magic link werkt lokaal niet prod | Site URL in Supabase staat nog op localhost |
| Cookies werken niet | Middleware draait niet — check matcher en path |
auth.uid() returnt null |
User niet ingelogd — check session in Server Component |
Tips
- Test isolation in incognito — alleen daar ben je écht "another user"
- RLS error messages kunnen vaag zijn — log policy + auth.uid() in Postgres function voor debug
- Productie env vars — vergeet niet je Vercel env vars te zetten
- Eindopdracht-koppeling: dit huiswerk is je startpunt — bouw eindopdracht hier op
- Veel succes met de eindopdracht!
Voor je begint aan de eindopdracht
Als afsluiting:
- Plan je eindopdracht concreet (Sectie 5 van AUTH.md is de basis)
- Start een nieuwe repo van scratch óf bouw verder op deze
- Push klein en vaak — niet één big commit aan eind
- Vraag hulp tijdig — Slack, mail, office hours
- Plan tijd in van min 8-10 uur/week voor 4-6 weken
Bedankt voor jullie inzet deze 18 lessen. Veel succes met de eindopdracht!