Files
novi-lessons/Les18-Supabase-Auth-RLS/Les18-Huiswerk.md
2026-06-03 16:58:25 +02:00

8.8 KiB
Raw Blame History

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

  • tasks tabel 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

  1. github.com → Settings → Developer settings → OAuth Apps → New
  2. Application name: jouw app
  3. Homepage URL: http://localhost:3000 (dev) of https://yourapp.vercel.app (prod)
  4. Callback URL: https://your-project.supabase.co/auth/v1/callback
  5. Save, generate Client Secret

B2 — Configure in Supabase

  1. Dashboard → Auth → Providers → GitHub
  2. 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:

  1. Push naar GitHub
  2. Vercel: import repo
  3. Add env vars: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY
  4. Deploy
  5. Update Supabase Auth → URL Configuration:
    • Site URL: https://yourapp.vercel.app
    • Redirect URLs: https://yourapp.vercel.app/**, http://localhost:3000/**
  6. 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)

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

  1. GitHub repo URL in Brightspace
  2. Vercel productie URL in Brightspace
  3. AUTH.md in repo-root
  4. Test-account credentials voor docent (email of GitHub)
  5. 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:

  1. Plan je eindopdracht concreet (Sectie 5 van AUTH.md is de basis)
  2. Start een nieuwe repo van scratch óf bouw verder op deze
  3. Push klein en vaak — niet één big commit aan eind
  4. Vraag hulp tijdig — Slack, mail, office hours
  5. 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!