- docs/scrum4me-styling.md → docs/design/styling.md - docs/MD3_Color_Scheme_Documentation.md → docs/design/md3-color-scheme.md - docs/API.md → docs/api/rest-contract.md - docs/scrum4me-test-plan.md → docs/qa/api-test-plan.md - docs/scrum4me-backlog.md → docs/backlog/index.md - docs/scrum4me-product-backlog.md → docs/backlog/product-historical.md - docs/erd.svg → docs/assets/erd.svg - docs/icons.html → docs/assets/icons.html Internal links updated across CLAUDE.md, README.md, docs/, prisma/, and scripts/. prisma/schema.prisma erd output path also updated.
4.1 KiB
Patroon: QR-pairing via unauth-SSE + pre-auth cookie
Het M10 QR-login-mechanisme is herbruikbaar voor elke feature die realtime- feedback wil tussen twee browsers/devices vóórdat de eindgebruiker is geauthenticeerd. De typische vorm:
"Apparaat A start een proces, krijgt een token. Apparaat B (bekend kanaal) bevestigt iets. Apparaat A wil dat realtime weten en daarna iets claimen."
Voorbeelden waar dit zou kunnen passen: device-pairing voor 2FA-setup, login- op-TV via QR, "claim deze export"-flow, account-overdracht tussen sessies.
Drie eindpunten
| Endpoint | Auth | Doel |
|---|---|---|
POST /api/.../start |
anon | maakt resource aan, retourneert mobile-secret in body, zet HttpOnly device-token cookie |
GET /api/.../stream/[id] |
cookie | SSE die op LISTEN/NOTIFY wacht op statusverandering |
POST /api/.../claim |
cookie | atomic state-transitie van "approved" → "consumed", wisselt cookie in voor échte sessie |
Plus een server-action-laag die door het tweede device wordt aangeroepen na het scannen / klikken van een link met fragment-secret.
Vier security-uitgangspunten
- Twee gescheiden geheimen — een voor het kanaal richting het tweede device (in QR-fragment), een voor het oorspronkelijke device (in HttpOnly cookie). Beide alleen als sha256-hash in DB.
- Geen secret in URL. Path en querystring lekken naar access logs,
reverse proxies, observability. Geheimen reizen alleen via:
- URL-fragment (
#…) — browsers sturen die niet naar de server - HttpOnly cookies — meestal niet gelogd, en alleen leesbaar door server
- POST-body — niet gelogd standaard
- URL-fragment (
- Atomic consume. Het claim-endpoint doet één UPDATE met een composite WHERE op alle invarianten (status, hash, expiry). PostgreSQL row-locking garandeert dat concurrent dubbele claims slechts één caller succes geven.
- Path-scoped cookie.
Path=/api/.../...zorgt dat de pre-auth cookie alleen naar pairing-routes gaat — niet naar de rest van de app.
Sjabloon-bestanden
Ga voor M10 specifiek? Kopieer en pas aan:
lib/auth/pairing.ts— secret/token generators + sha256 + timing-safe verify + expiry helperlib/auth/pair-cookie.ts— set/read/clear van Path-scoped HttpOnly cookieapp/api/auth/pair/start/route.ts— anon POST, rate-limited, sets cookieapp/api/auth/pair/stream/[id]/route.ts— SSE met cookie-auth, LISTEN op eigen channelapp/api/auth/pair/claim/route.ts— atomic update + iron-session schrijvenactions/pairing.ts— Server Actions voor het tweede deviceapp/(app)/m/pair/pair-confirmation.tsx— Client island dielocation.hashparseert
Voor het tweede device zit de auth meestal al in de bestaande (app)-layout
guard. De Client Component gebruikt window.location.hash (niet useSearchParams)
om het secret op te pikken.
TTL-richtlijn
Drie tijden in escalerende volgorde, alle korter dan de reguliere sessie:
- Pending (cookie + DB-rij) — kort genoeg dat een verloren cookie/QR weinig schade aanricht. M10: 5 minuten.
- Approved (na bevestiging) — kort genoeg dat een approved-maar-niet- geclaimde pairing niet eindeloos open blijft. M10: 5 minuten extra.
- Resulterende sessie — kort genoeg voor publieke apparaten, lang genoeg
voor een werkdag. M10: 8 uur, plus
paired: true-vlag voor toekomstige remote-revoke.
Wanneer dit patroon NIET gebruiken
- Wanneer beide kanten al ingelogd zijn — dan is een normaal API-call met bestaande sessie eenvoudiger.
- Wanneer realtime niet kritiek is — een korte poll (
setIntervalop een status-endpoint) is simpeler dan een SSE-stream. - Wanneer er één centraal apparaat is — gebruik dan een normale sessie; de twee-device-dans is alleen nodig om credentials van het ene apparaat naar het andere te brengen.
Referenties
- Volledige flow + threat-model:
docs/scrum4me-architecture.md§ QR-pairing flow - Endpoint-contract:
docs/api/rest-contract.md§ Auth — QR-pairing - LISTEN/NOTIFY-pattern:
app/api/realtime/solo/route.ts(M8 ST-802) — zelfde ReadableStream + heartbeat + hard-close + abort-cleanup, alleen ander channel