Files
triqura-ecd/docs/specs/archive/bouwplan-auth-flow-complete-v1.0.md
colinislit 2505b27437 feat: add duplicate email detection via auth hook with fallback
- 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
2025-11-19 11:03:46 +01:00

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:

  1. Signup Flow - Magic Link (passwordless onboarding)
  2. Login Flow - Email + Password (returning users)
  3. Password Reset - Forgot password → Email → New password
  4. Email Verification - Confirm email na signup
  5. Demo Account - One-click quick access
  6. 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 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)

  1. Confirm Signup (Magic Link)
  2. Reset Password
  3. 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)

  1. E1.S1-S4: Refactor login page
  2. E2.S1-S2: Signup flow (magic link)
  3. E3.S1: Login flow (password)

Checkpoint: Basic signup + login werkt

Phase 2: Password Management (2-3 uur)

  1. E4.S1-S4: Password reset flow
  2. E3.S2: Set password after signup

Checkpoint: Complete password lifecycle

Phase 3: Security & Demo (2 uur)

  1. E5.S1-S2: Email verification
  2. E6.S1-S2: Demo account + banner

Checkpoint: Production-ready security

Phase 4: Polish (1-2 uur)

  1. 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:

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)