- Add before-user-created hook function for server-side duplicate email detection - Implement fallback detection using identities array check (Supabase limitation) - Update signUpWithPassword to detect duplicate emails via hook errors or empty identities - Add error handling in login page to show duplicate email errors and auto-switch to login mode - Add auth hook setup documentation and test scripts - Add password reset and update password flows - Add email templates for signup confirmation and password reset
30 KiB
🚀 Bouwplan — Volledige Auth Flow + Demo Account
Projectnaam: Mini EPD Demo Platform - Complete Authenticatie Systeem Versie: v1.0 Datum: 18-01-2025 Auteur: Colin (met Claude Code) Scope: Production-ready auth flow met signup, login, password reset + demo account convenience
1. Doel en Context
🎯 Doel: Een complete, production-ready authenticatie flow bouwen die professionele development skills showcaset, met een demo account voor quick access.
📘 Toelichting:
Portfolio Waarde:
- Demonstreert full-stack auth expertise
- Security best practices (password hashing, email verification, rate limiting)
- Professional UX (duidelijke error states, loading indicators, success feedback)
- Production-ready mindset (edge cases, error handling, rollback scenarios)
Feature Scope:
- ✅ Signup Flow - Magic Link (passwordless onboarding)
- ✅ Login Flow - Email + Password (returning users)
- ✅ Password Reset - Forgot password → Email → New password
- ✅ Email Verification - Confirm email na signup
- ✅ Demo Account - One-click quick access
- ✅ Session Management - Auto-refresh, secure cookies, logout
User Journeys:
- Recruiter/Prospect: Quick Demo → Impressed → Account aanmaken
- New User: Email → Magic Link → Account created → Onboarding
- Returning User: Email + Password → Dashboard
- Forgot Password: Reset link → New password → Login
2. Uitgangspunten
2.1 Technische Stack
Bestaand (blijft):
- Next.js 16.0.1 + React 19 + TypeScript
- Supabase Auth (backend)
- Tailwind CSS (styling)
- Vercel (hosting)
Nieuw (toe te voegen):
- Zod voor form validation
- React Hook Form (optioneel - betere UX)
- Email templates (Supabase)
2.2 Projectkaders
- Tijd: 8-10 uur voor volledige implementatie + testing
- Team: 1 developer (zelfstandig uit te voeren)
- Breaking changes: GEEN - Bestaande auth blijft werken tijdens migratie
- Deployment: Phased rollout (feature flags mogelijk)
- Security: Production standards (OWASP top 10)
2.3 Programmeer Uitgangspunten
Code Quality:
- ✅ DRY: Herbruikbare auth componenten en hooks
- ✅ KISS: Gebruik Supabase built-in features waar mogelijk
- ✅ SOC: Auth logic gescheiden van UI components
- ✅ YAGNI: Geen OAuth/SSO (kan later), focus op email auth
Security Principles:
- Passwords NOOIT in plain text (Supabase handled dit)
- Rate limiting op sensitive endpoints
- HTTPS only (Vercel default)
- Secure session cookies (HTTP-only, SameSite)
- Input validation (email format, password strength)
- CSRF protection (Next.js built-in)
UX Principles:
- Loading states op alle async operaties
- Clear error messages (user-friendly, geen technical jargon)
- Success feedback (toasts/messages)
- Keyboard accessible (tab order, focus states)
- Mobile responsive (touch-friendly buttons)
3. Epics & Stories Overzicht
| Epic ID | Titel | Doel | Status | Stories | Geschatte Tijd |
|---|---|---|---|---|---|
| E1 | Login/Signup Page Refactor | Unified auth page met tabs/modes | ⏳ To Do | 4 | 2 uur |
| E2 | Signup Flow (Magic Link) | Passwordless account creation | ⏳ To Do | 3 | 2 uur |
| E3 | Login Flow (Password) | Returning user login | ⏳ To Do | 2 | 1 uur |
| E4 | Password Reset Flow | Forgot password → reset email → update | ⏳ To Do | 4 | 2.5 uur |
| E5 | Email Verification | Confirm email after signup | ⏳ To Do | 2 | 1 uur |
| E6 | Demo Account | Quick access button | ⏳ To Do | 2 | 1 uur |
| E7 | Testing & Polish | All flows tested, edge cases handled | ⏳ To Do | 3 | 1.5 uur |
Totale schatting: 9-11 uur werk
4. Epics & Stories (Uitwerking)
Epic 1 — Login/Signup Page Refactor
Epic Doel: Unified auth page die signup, login en demo ondersteunt met duidelijke modes.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E1.S1 | Design nieuwe page structuur | Wireframe + component breakdown | ⏳ | 1 |
| E1.S2 | Implementeer mode switcher | Toggle tussen "Nieuw account" / "Inloggen" | ⏳ | 2 |
| E1.S3 | Basis form components | Email input, password input, submit button | ⏳ | 2 |
| E1.S4 | Error/success messaging | Toast/banner component voor feedback | ⏳ | 2 |
Technical Notes:
E1.S1 - Page Structuur:
┌──────────────────────────────────────────┐
│ Mini EPD - Professioneel Elektronisch │
│ Patiënten Dossier │
├──────────────────────────────────────────┤
│ │
│ [🎭 Quick Demo - Bekijk Prototype] │ ← Prominent, secundair
│ │
│ ────── of ────── │
│ │
│ ( ) Nieuw account aanmaken │ ← Radio/Tab
│ (•) Inloggen │
│ │
│ 📧 Email │
│ [_________________________] │
│ │
│ 🔒 Wachtwoord │ ← Alleen bij "Inloggen"
│ [_________________________] │
│ │
│ [Inloggen] of [Account Aanmaken] │ ← Dynamic button text
│ │
│ Wachtwoord vergeten? [Reset] │ ← Link naar /reset-password
│ │
└──────────────────────────────────────────┘
E1.S2 - Mode State Management:
type AuthMode = 'signup' | 'login'
const [mode, setMode] = useState<AuthMode>('login') // Default = login
// Dynamic UI based on mode
const showPasswordField = mode === 'login'
const buttonText = mode === 'login' ? 'Inloggen' : 'Account Aanmaken'
const submitHandler = mode === 'login' ? handleLogin : handleSignup
E1.S3 - Form Components:
// Input component met validation states
<Input
type="email"
label="Email"
value={email}
onChange={setEmail}
error={emailError}
required
/>
<Input
type="password"
label="Wachtwoord"
value={password}
onChange={setPassword}
error={passwordError}
showStrength={mode === 'login' ? false : true}
required
/>
E1.S4 - Messaging System:
// Toast/banner component
type Message = {
type: 'success' | 'error' | 'info'
text: string
duration?: number
}
// Usage examples:
showMessage({
type: 'success',
text: 'Check je email voor de magic link!'
})
showMessage({
type: 'error',
text: 'Email of wachtwoord incorrect'
})
Epic 2 — Signup Flow (Magic Link)
Epic Doel: Passwordless signup via magic link in email (Supabase OTP).
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E2.S1 | Implementeer signup handler | Email submit → Supabase OTP → Success message | ⏳ | 3 |
| E2.S2 | Email callback handler | Magic link → account created → redirect dashboard | ⏳ | 2 |
| E2.S3 | Custom email template | Branded email met clear CTA | ⏳ | 2 |
Technical Notes:
E2.S1 - Signup Handler:
// app/login/page.tsx
async function handleSignup(email: string) {
setLoading(true)
try {
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
shouldCreateUser: true,
data: {
// Optional: Store extra user metadata
source: 'web_signup',
timestamp: new Date().toISOString(),
}
}
})
if (error) throw error
setMessage({
type: 'success',
text: `Check je email (${email}) voor de magic link!`
})
// Optional: Track analytics
trackEvent('signup_initiated', { email })
} catch (error) {
setMessage({
type: 'error',
text: error.message || 'Signup mislukt. Probeer opnieuw.'
})
} finally {
setLoading(false)
}
}
E2.S2 - Callback Handler:
// app/auth/callback/route.ts (EXISTING - update if needed)
export async function GET(request: Request) {
const requestUrl = new URL(request.url)
const code = requestUrl.searchParams.get('code')
if (code) {
const supabase = createServerClient()
const { data, error } = await supabase.auth.exchangeCodeForSession(code)
if (!error && data.user) {
// Check if this is first login (new user)
const isNewUser = data.user.created_at === data.user.last_sign_in_at
if (isNewUser) {
// Redirect to onboarding
return NextResponse.redirect(`${requestUrl.origin}/onboarding`)
} else {
// Redirect to dashboard
return NextResponse.redirect(`${requestUrl.origin}/epd/clients`)
}
}
}
// Error fallback
return NextResponse.redirect(`${requestUrl.origin}/login?error=auth_failed`)
}
E2.S3 - Email Template (Supabase Dashboard):
<!-- Supabase → Authentication → Email Templates → Magic Link -->
<h2>Welkom bij Mini EPD!</h2>
<p>Je hebt een account aangemaakt. Klik op de knop hieronder om je email te bevestigen en in te loggen:</p>
<a href="{{ .ConfirmationURL }}"
style="background: #0d9488; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
Bevestig Email & Login
</a>
<p style="color: #64748b; font-size: 14px;">
Deze link is 1 uur geldig. Heb je deze email niet aangevraagd? Negeer deze email.
</p>
<p style="color: #64748b; font-size: 12px; margin-top: 24px;">
Mini EPD - Professioneel Elektronisch Patiënten Dossier
</p>
Epic 3 — Login Flow (Password)
Epic Doel: Returning users kunnen inloggen met email + wachtwoord.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E3.S1 | Implementeer login handler | Email + password → Supabase auth → redirect | ⏳ | 2 |
| E3.S2 | Set password flow | Na magic link signup → optie om password te setten | ⏳ | 3 |
Technical Notes:
E3.S1 - Login Handler:
// app/login/page.tsx
async function handleLogin(email: string, password: string) {
setLoading(true)
// Validation
if (!email || !password) {
setMessage({
type: 'error',
text: 'Vul email en wachtwoord in'
})
setLoading(false)
return
}
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) throw error
setMessage({
type: 'success',
text: 'Ingelogd! Redirect naar dashboard...'
})
// Track analytics
trackEvent('login_success', { method: 'password' })
// Redirect after 500ms
setTimeout(() => {
router.push('/epd/clients')
}, 500)
} catch (error) {
setMessage({
type: 'error',
text: 'Email of wachtwoord incorrect'
})
// Track failed login (rate limiting check)
trackEvent('login_failed', { email })
} finally {
setLoading(false)
}
}
E3.S2 - Set Password Flow:
// app/set-password/page.tsx (NEW)
'use client'
export default function SetPasswordPage() {
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
const router = useRouter()
async function handleSetPassword() {
// Validation
if (password !== confirmPassword) {
setMessage({ type: 'error', text: 'Wachtwoorden komen niet overeen' })
return
}
if (password.length < 8) {
setMessage({ type: 'error', text: 'Wachtwoord moet minimaal 8 tekens zijn' })
return
}
setLoading(true)
try {
const { error } = await supabase.auth.updateUser({
password
})
if (error) throw error
setMessage({
type: 'success',
text: 'Wachtwoord ingesteld! Je kunt nu inloggen met email en wachtwoord.'
})
setTimeout(() => router.push('/epd/clients'), 2000)
} catch (error) {
setMessage({ type: 'error', text: error.message })
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md w-full space-y-6">
<h1>Stel een wachtwoord in</h1>
<p>Gebruik dit wachtwoord om later in te loggen</p>
<Input
type="password"
label="Nieuw wachtwoord"
value={password}
onChange={setPassword}
showStrength
/>
<Input
type="password"
label="Bevestig wachtwoord"
value={confirmPassword}
onChange={setConfirmPassword}
/>
<Button onClick={handleSetPassword} loading={loading}>
Wachtwoord Instellen
</Button>
<button onClick={() => router.push('/epd/clients')}>
Sla over (blijf magic link gebruiken)
</button>
</div>
</div>
)
}
Epic 4 — Password Reset Flow
Epic Doel: Users kunnen vergeten wachtwoord resetten via email link.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E4.S1 | Reset request page | Email input → Send reset link | ⏳ | 2 |
| E4.S2 | Reset email template | Branded email met reset link | ⏳ | 1 |
| E4.S3 | Reset password page | New password form + validation | ⏳ | 3 |
| E4.S4 | Update password handler | Supabase update + redirect | ⏳ | 2 |
Technical Notes:
E4.S1 - Reset Request Page:
// app/reset-password/page.tsx
'use client'
export default function ResetPasswordPage() {
const [email, setEmail] = useState('')
const [loading, setLoading] = useState(false)
const [sent, setSent] = useState(false)
async function handleResetRequest() {
if (!email) {
setMessage({ type: 'error', text: 'Vul je email in' })
return
}
setLoading(true)
try {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/update-password`
})
if (error) throw error
setSent(true)
setMessage({
type: 'success',
text: `Reset link verstuurd naar ${email}. Check je inbox!`
})
} catch (error) {
setMessage({ type: 'error', text: error.message })
} finally {
setLoading(false)
}
}
if (sent) {
return (
<div className="text-center space-y-4">
<h2>Email verstuurd! ✉️</h2>
<p>Check je inbox voor de reset link.</p>
<p className="text-sm text-slate-600">
Niet ontvangen? Check je spam folder of
<button onClick={() => setSent(false)}>probeer opnieuw</button>
</p>
</div>
)
}
return (
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md w-full space-y-6">
<h1>Wachtwoord Vergeten</h1>
<p>Vul je email in en we sturen je een reset link.</p>
<Input
type="email"
label="Email"
value={email}
onChange={setEmail}
placeholder="jouw@email.com"
/>
<Button onClick={handleResetRequest} loading={loading} fullWidth>
Stuur Reset Link
</Button>
<Link href="/login" className="text-sm text-teal-600">
← Terug naar login
</Link>
</div>
</div>
)
}
E4.S2 - Reset Email Template:
<!-- Supabase → Authentication → Email Templates → Reset Password -->
<h2>Wachtwoord Resetten</h2>
<p>Je hebt een wachtwoord reset aangevraagd voor je Mini EPD account.</p>
<a href="{{ .ConfirmationURL }}"
style="background: #0d9488; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
Reset Wachtwoord
</a>
<p style="color: #64748b; font-size: 14px;">
Deze link is 1 uur geldig. Heb je deze reset niet aangevraagd? Negeer deze email - je wachtwoord blijft ongewijzigd.
</p>
E4.S3 - Update Password Page:
// app/update-password/page.tsx
'use client'
export default function UpdatePasswordPage() {
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [loading, setLoading] = useState(false)
const router = useRouter()
async function handleUpdatePassword() {
// Validation
if (password !== confirmPassword) {
setMessage({ type: 'error', text: 'Wachtwoorden komen niet overeen' })
return
}
if (password.length < 8) {
setMessage({ type: 'error', text: 'Wachtwoord minimaal 8 tekens' })
return
}
setLoading(true)
try {
const { error } = await supabase.auth.updateUser({
password
})
if (error) throw error
setMessage({
type: 'success',
text: 'Wachtwoord gewijzigd! Je kunt nu inloggen.'
})
setTimeout(() => router.push('/login'), 2000)
} catch (error) {
setMessage({ type: 'error', text: error.message })
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md w-full space-y-6">
<h1>Nieuw Wachtwoord Instellen</h1>
<Input
type="password"
label="Nieuw wachtwoord"
value={password}
onChange={setPassword}
showStrength
/>
<Input
type="password"
label="Bevestig wachtwoord"
value={confirmPassword}
onChange={setConfirmPassword}
/>
<Button onClick={handleUpdatePassword} loading={loading} fullWidth>
Wachtwoord Wijzigen
</Button>
</div>
</div>
)
}
Epic 5 — Email Verification
Epic Doel: Confirm email na signup (security + anti-spam).
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E5.S1 | Enable email confirmation | Supabase settings + email template | ⏳ | 1 |
| E5.S2 | Unverified state handling | Block app access until confirmed | ⏳ | 2 |
Technical Notes:
E5.S1 - Enable Confirmation:
# Supabase Dashboard:
# Authentication → Settings → Email Auth
# ✅ Enable email confirmations
# ✅ Secure email change
E5.S2 - Verification Check:
// middleware.ts (UPDATE)
export async function middleware(request: NextRequest) {
const supabase = createServerClient()
const { data: { user } } = await supabase.auth.getUser()
// Check if email is verified
if (user && !user.email_confirmed_at) {
// Redirect to verification notice page
return NextResponse.redirect(new URL('/verify-email', request.url))
}
// Rest of middleware logic...
}
// app/verify-email/page.tsx (NEW)
export default function VerifyEmailPage() {
const [resending, setResending] = useState(false)
async function resendVerification() {
setResending(true)
// Trigger resend via Supabase
await supabase.auth.resend({
type: 'signup',
email: user.email
})
setResending(false)
}
return (
<div className="min-h-screen flex items-center justify-center">
<div className="max-w-md text-center space-y-6">
<h1>Email Verificatie Vereist</h1>
<p>Check je inbox voor de verificatie link.</p>
<Button onClick={resendVerification} loading={resending}>
Verstuur Opnieuw
</Button>
<Link href="/login">← Terug naar login</Link>
</div>
</div>
)
}
Epic 6 — Demo Account
Epic Doel: Quick demo access button voor recruiters/prospects.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E6.S1 | QuickDemoButton component | One-click demo login | ⏳ | 2 |
| E6.S2 | Demo mode indicator | Banner in EPD app | ⏳ | 1 |
Technical Notes:
E6.S1 - Quick Demo Button:
// app/login/components/quick-demo-button.tsx
export function QuickDemoButton() {
const [loading, setLoading] = useState(false)
const router = useRouter()
async function handleQuickDemo() {
setLoading(true)
try {
const { error } = await supabase.auth.signInWithPassword({
email: 'demo@mini-ecd.demo',
password: 'Demo2024!'
})
if (error) throw error
trackEvent('demo_access')
router.push('/epd/clients?demo=true')
} catch (error) {
setMessage({ type: 'error', text: 'Demo login mislukt' })
} finally {
setLoading(false)
}
}
return (
<button
onClick={handleQuickDemo}
disabled={loading}
className="w-full py-3 px-4 bg-amber-100 border-2 border-amber-300 rounded-lg hover:bg-amber-200 transition-colors"
>
{loading ? (
<Loader2 className="animate-spin mx-auto" />
) : (
<span className="flex items-center justify-center gap-2">
🎭 Quick Demo - Bekijk Prototype
</span>
)}
</button>
)
}
E6.S2 - Demo Banner:
// app/epd/components/demo-banner.tsx
export function DemoBanner() {
const searchParams = useSearchParams()
const isDemo = searchParams.get('demo') === 'true'
if (!isDemo) return null
return (
<div className="bg-amber-50 border-b border-amber-200 px-4 py-2">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<span className="text-sm text-amber-800">
🎭 Je bekijkt het prototype met demo data
</span>
<Link
href="/login"
className="text-sm font-medium text-teal-600 hover:text-teal-700"
>
Maak gratis account →
</Link>
</div>
</div>
)
}
Epic 7 — Testing & Polish
Epic Doel: Alle flows grondig testen + edge cases + UX polish.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Story Points |
|---|---|---|---|---|
| E7.S1 | Test alle happy flows | Signup, login, reset, demo werken | ⏳ | 2 |
| E7.S2 | Test error scenarios | Network errors, invalid input, rate limits | ⏳ | 2 |
| E7.S3 | UX polish | Loading states, animations, responsive | ⏳ | 2 |
Test Cases:
| Test ID | Flow | Scenario | Expected Result |
|---|---|---|---|
| TC1 | Signup | Valid email → Magic link | Success message, email sent |
| TC2 | Signup | Invalid email format | Validation error |
| TC3 | Signup | Email already exists | "Account bestaat al - probeer in te loggen" |
| TC4 | Login | Valid credentials | Redirect to dashboard |
| TC5 | Login | Invalid password | "Email of wachtwoord incorrect" |
| TC6 | Login | Unverified email | Redirect to verify-email page |
| TC7 | Password Reset | Valid email | Reset link sent |
| TC8 | Password Reset | Email not found | Still show success (security) |
| TC9 | Password Reset | Weak new password | Validation error "Min 8 characters" |
| TC10 | Demo | Quick demo button | Instant login, redirect with ?demo=true |
| TC11 | Session | Auto-refresh token | Session stays valid 24h+ |
| TC12 | Logout | Click logout | Session cleared, redirect to login |
5. Kwaliteit & Testplan
Security Checklist
- Passwords hashed (Supabase handles)
- Rate limiting on sensitive endpoints
- HTTPS only (Vercel default)
- HTTP-only secure cookies
- Input validation (email, password strength)
- CSRF protection (Next.js built-in)
- No sensitive data in client-side code
- Error messages don't leak info ("Email or password incorrect" not "Email not found")
Performance Checklist
- Auth state loaded < 500ms
- Form submissions < 2s response
- Optimistic UI updates
- Debounced email validation
- Code splitting (lazy load auth pages)
UX Checklist
- Clear loading states (spinners)
- Success feedback (toasts/messages)
- Error messages user-friendly
- Keyboard accessible (tab order)
- Mobile responsive (touch targets)
- Focus management (auto-focus first input)
- Password visibility toggle
- Password strength indicator
6. Routes & File Structure
New Routes
app/
├── login/page.tsx # Main auth page (signup/login/demo)
├── reset-password/page.tsx # Request reset link
├── update-password/page.tsx # Set new password (from email)
├── set-password/page.tsx # Set password after magic link signup
├── verify-email/page.tsx # Email verification notice
├── onboarding/page.tsx # First-time user onboarding (optional)
├── auth/
│ ├── callback/route.ts # Magic link callback (EXISTING)
│ └── logout/route.ts # Logout handler (EXISTING)
Components
components/auth/
├── auth-form.tsx # Main form component
├── quick-demo-button.tsx # Demo access button
├── password-strength.tsx # Password strength indicator
├── auth-message.tsx # Success/error message component
└── mode-switcher.tsx # Toggle between signup/login
7. Supabase Configuration
Email Templates (To Configure)
- Confirm Signup (Magic Link)
- Reset Password
- Email Change Confirmation
Settings (To Enable)
# Supabase Dashboard → Authentication → Settings
✅ Enable email confirmations
✅ Enable email change confirmations
✅ Secure email change (require password)
# Email Auth Settings
Rate limit: 4 emails per hour (default)
Confirmation expiry: 1 hour
RLS Policies (Verify)
-- Users can only see their own data
CREATE POLICY "Users can view own data" ON profiles
FOR SELECT USING (auth.uid() = user_id);
-- Users can update own profile
CREATE POLICY "Users can update own data" ON profiles
FOR UPDATE USING (auth.uid() = user_id);
8. Risico's & Mitigatie
| Risico | Kans | Impact | Mitigatie |
|---|---|---|---|
| Email delivery issues | Middel | Hoog | - Demo account fallback - "Resend email" buttons - Clear "check spam" messaging |
| Rate limiting blocks users | Laag | Middel | - Increase Supabase limits - Clear error message with wait time |
| Password reset abuse | Middel | Laag | - Rate limiting (built-in) - Track suspicious activity |
| Email enumeration | Middel | Laag | - Same message for existing/non-existing emails - No "email not found" errors |
| Session hijacking | Laag | Hoog | - HTTP-only cookies - Short token expiry - Secure flag on cookies |
| Broken magic links | Laag | Middel | - 24h expiry (reasonable) - Clear error page - "Request new link" option |
9. Definition of Done
All Epics (E1-E7) zijn compleet wanneer:
✅ Functional Requirements:
- Signup flow werkt (magic link → account created)
- Login flow werkt (email + password → dashboard)
- Password reset flow werkt (request → email → new password)
- Email verification enforced (unverified users blocked)
- Demo account accessible (one-click)
- Session management werkt (auto-refresh, logout)
✅ Security Requirements:
- All OWASP top 10 addressed
- No sensitive data in client code
- Rate limiting active
- Secure cookies configured
✅ UX Requirements:
- All flows tested on mobile + desktop
- Loading states smooth
- Error messages clear
- Keyboard accessible
- No console errors
✅ Code Quality:
- TypeScript strict mode (no
any) - Reusable components extracted
- Error handling comprehensive
- Code commented where complex
✅ Documentation:
- README updated with auth flow docs
- Environment variables documented
- Supabase setup guide created
✅ Deployment:
- Tested on staging environment
- Production deployment successful
- Smoke tests passed
- Rollback plan ready
10. Implementatie Volgorde
Aanbevolen phased rollout:
Phase 1: Foundation (3-4 uur)
- E1.S1-S4: Refactor login page
- E2.S1-S2: Signup flow (magic link)
- E3.S1: Login flow (password)
Checkpoint: Basic signup + login werkt
Phase 2: Password Management (2-3 uur)
- E4.S1-S4: Password reset flow
- E3.S2: Set password after signup
Checkpoint: Complete password lifecycle
Phase 3: Security & Demo (2 uur)
- E5.S1-S2: Email verification
- E6.S1-S2: Demo account + banner
Checkpoint: Production-ready security
Phase 4: Polish (1-2 uur)
- E7.S1-S3: Testing + UX polish
Checkpoint: All flows tested, ready to showcase
Total: 8-11 uur
11. Referenties
Mission Control Documents:
- PRD:
docs/specs/prd-mini-ecd-v1.2.md - TO:
docs/specs/to-mini-ecd-v1_2.md
External Resources:
- Supabase Auth Docs: https://supabase.com/docs/guides/auth
- Next.js SSR Auth: https://supabase.com/docs/guides/auth/server-side
- OWASP Top 10: https://owasp.org/www-project-top-ten/
Code References:
- Login page:
app/login/page.tsx(MAJOR REFACTOR) - Auth callback:
app/auth/callback/route.ts(MINOR UPDATE) - Middleware:
middleware.ts(UPDATE - email verification check) - Auth client:
lib/auth/client.ts(EXTEND)
12. Versiehistorie
| Versie | Datum | Auteur | Wijziging |
|---|---|---|---|
| v1.0 | 18-01-2025 | Colin | Initiële versie - Volledige auth flow bouwplan |
Status: ⏳ Ready for Implementation Next Steps: Start met E1.S1 (Design nieuwe page structuur) Portfolio Value: 🔥🔥🔥🔥🔥 (High - showcases full-stack auth expertise)