# Luna Recipes — Auth v2 Feature-Spezifikation ## Übersicht Implementierung eines vollständigen Authentifizierungssystems für die Luna Rezept-App mit Multi-User-Support und Haushaltsfunktionalität. Das System ermöglicht es mehreren Nutzern, die App zu verwenden, dabei ihre Daten zu trennen und optional einen gemeinsamen Haushalt zu teilen. ## 1. Datenmodell ### 1.1 Neue Tabellen #### users ```sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, display_name TEXT NOT NULL, avatar_url TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE UNIQUE INDEX idx_users_email ON users(email); ``` #### households ```sql CREATE TABLE households ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, invite_code TEXT UNIQUE NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE UNIQUE INDEX idx_households_invite_code ON households(invite_code); ``` #### household_members ```sql CREATE TABLE household_members ( household_id UUID REFERENCES households(id) ON DELETE CASCADE, user_id UUID REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN ('owner', 'member')), joined_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (household_id, user_id) ); ``` #### user_favorites (Neue Tabelle) ```sql CREATE TABLE user_favorites ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, recipe_id UUID REFERENCES recipes(id) ON DELETE CASCADE, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, recipe_id) ); ``` ### 1.2 Erweiterte bestehende Tabellen #### shopping_items ```sql -- Erweitern um User- und Haushaltszuordnung ALTER TABLE shopping_items ADD COLUMN user_id UUID REFERENCES users(id); ALTER TABLE shopping_items ADD COLUMN household_id UUID REFERENCES households(id); -- Migration: NULL user_id wird beim ersten Login zugewiesen ``` #### notes ```sql -- Erweitern um User-Zuordnung ALTER TABLE notes ADD COLUMN user_id UUID REFERENCES users(id); -- Migration: Bestehende Notizen werden dem ersten registrierten User zugewiesen ``` #### recipes ```sql -- Recipes bleiben global, aber mit Ersteller-Info ALTER TABLE recipes ADD COLUMN created_by UUID REFERENCES users(id); -- Migration: Bestehende Rezepte werden dem ersten registrierten User zugewiesen ``` ### 1.3 Daten-Ownership - **Recipes:** Global sichtbar, `created_by` zur Information - **Shopping Items:** Pro User ODER pro Haushalt (wenn `household_id` gesetzt) - **Favorites:** Pro User (user_favorites Tabelle) - **Notes:** Pro User - **Households:** Gemeinsam für alle Mitglieder ## 2. API Endpoints ### 2.1 Authentication #### POST /api/auth/register ```json // Request { "email": "luna@example.com", "password": "secure123!", "display_name": "Luna" } // Response (201 Created) { "success": true, "user": { "id": "uuid-here", "email": "luna@example.com", "display_name": "Luna", "avatar_url": null, "created_at": "2026-02-18T15:30:00Z" }, "access_token": "jwt-token-here" } // Error (400 Bad Request) { "success": false, "error": "EMAIL_EXISTS", "message": "Ein Account mit dieser E-Mail existiert bereits" } ``` #### POST /api/auth/login ```json // Request { "email": "luna@example.com", "password": "secure123!" } // Response (200 OK) { "success": true, "user": { "id": "uuid-here", "email": "luna@example.com", "display_name": "Luna", "avatar_url": null }, "access_token": "jwt-token-here" } // Error (401 Unauthorized) { "success": false, "error": "INVALID_CREDENTIALS", "message": "E-Mail oder Passwort ungültig" } ``` #### POST /api/auth/logout ```json // Response (200 OK) { "success": true, "message": "Erfolgreich abgemeldet" } ``` #### GET /api/auth/me ```json // Response (200 OK) { "success": true, "user": { "id": "uuid-here", "email": "luna@example.com", "display_name": "Luna", "avatar_url": "https://example.com/avatar.jpg", "household": { "id": "household-uuid", "name": "Luna & Marc", "role": "owner" } } } ``` #### PUT /api/auth/me ```json // Request (Profil bearbeiten) { "display_name": "Luna Schmidt", "avatar_url": "https://example.com/new-avatar.jpg" } // Response (200 OK) { "success": true, "user": { "id": "uuid-here", "email": "luna@example.com", "display_name": "Luna Schmidt", "avatar_url": "https://example.com/new-avatar.jpg" } } ``` #### PUT /api/auth/me/password ```json // Request { "current_password": "old123!", "new_password": "new456!" } // Response (200 OK) { "success": true, "message": "Passwort erfolgreich geändert" } // Error (400 Bad Request) { "success": false, "error": "INVALID_CURRENT_PASSWORD", "message": "Aktuelles Passwort ist falsch" } ``` ### 2.2 Households #### POST /api/households ```json // Request (Haushalt erstellen) { "name": "Luna & Marc" } // Response (201 Created) { "success": true, "household": { "id": "household-uuid", "name": "Luna & Marc", "invite_code": "COOK2024", "created_at": "2026-02-18T15:30:00Z", "members": [ { "user_id": "user-uuid", "display_name": "Luna", "role": "owner", "joined_at": "2026-02-18T15:30:00Z" } ] } } ``` #### POST /api/households/:id/invite ```json // Response (200 OK) - Neuen Einladungscode generieren { "success": true, "invite_code": "COOK2025", "expires_at": "2026-02-25T15:30:00Z" } ``` #### POST /api/households/join ```json // Request { "invite_code": "COOK2024" } // Response (200 OK) { "success": true, "household": { "id": "household-uuid", "name": "Luna & Marc", "role": "member" } } // Error (400 Bad Request) { "success": false, "error": "INVALID_INVITE_CODE", "message": "Einladungscode ungültig oder abgelaufen" } ``` #### GET /api/households/mine ```json // Response (200 OK) { "success": true, "household": { "id": "household-uuid", "name": "Luna & Marc", "invite_code": "COOK2024", "role": "owner", "members": [ { "user_id": "user1-uuid", "display_name": "Luna", "role": "owner", "joined_at": "2026-02-18T15:30:00Z" }, { "user_id": "user2-uuid", "display_name": "Marc", "role": "member", "joined_at": "2026-02-19T10:15:00Z" } ] } } ``` ## 3. Frontend Pages ### 3.1 Authentication Pages #### /login - **Design:** Vollbild-Login mit Luna Recipes Logo - **Felder:** E-Mail, Passwort, "Angemeldet bleiben" Checkbox - **Actions:** Login, "Registrieren" Link, "Passwort vergessen" Link (v2.1) - **Mobile:** Touch-optimierte Input-Felder, große Login-Button - **Validation:** Client-side Validation mit sofortigem Feedback - **States:** Loading State beim Login-Prozess #### /register - **Design:** Gleiche Basis wie Login-Page - **Felder:** E-Mail, Passwort, Passwort wiederholen, Display Name - **Validation:** - E-Mail Format-Check - Passwort-Stärke Indikator - Passwort-Match Validation - Display Name min. 2 Zeichen - **Actions:** Registrieren, "Schon Account?" Login-Link - **Flow:** Nach erfolgreicher Registrierung → automatisch eingeloggt → Dashboard ### 3.2 Profile Pages #### /profile - **Layout:** Card-basiert, vertikal gestapelt (kein Tab-Interface) - **Profil-Karte:** - Avatar (Upload oder URL) - Display Name (inline editierbar) - E-Mail (nicht editierbar, mit "Ändern" Link für v2.1) - "Passwort ändern" Button - **Haushalt-Karte:** (immer sichtbar, direkt unter Profil) - **Kein Haushalt:** Card mit 🏠 Icon, "Haushalt erstellen" + "Beitreten" Buttons - **In Haushalt:** Haushalt-Name, Mitglieder-Avatare, "Verwalten" Button → `/profile/household` - Visuell prominent — nicht versteckt in einem Tab! - **Quick-Actions:** Logout-Button unten - **Mobile:** Große Avatar-Anzeige, Touch-freundliche Edit-Buttons, 44px Targets #### /profile/edit - **Modal oder Fullscreen (Mobile):** Profil bearbeiten - **Felder:** Display Name, Avatar URL/Upload - **Actions:** Speichern, Abbrechen - **Validation:** Display Name required #### /profile/password - **Layout:** Focused Passwort-Ändern Page - **Felder:** Aktuelles Passwort, Neues Passwort, Bestätigung - **Security:** - Passwort-Stärke Anzeige - "Passwort anzeigen" Toggle - Session-Refresh nach Änderung - **UX:** Erfolgs-Toast + Redirect nach Speichern #### /profile/household - **States:** - **Kein Haushalt:** "Haushalt erstellen" oder "Haushalt beitreten" - **Haushalt Owner:** Mitglieder-Liste, Einladungscode verwalten - **Haushalt Member:** Mitglieder-Liste, "Haushalt verlassen" - **Features:** - Einladungscode kopieren/teilen - QR-Code für Einladung (Mobile-optimiert) - Mitglieder-Management (nur Owner) ## 4. Authentication Flow ### 4.1 Token-Strategy #### JWT Access Token - **Laufzeit:** 15 Minuten - **Storage:** Memory (React State + Context) - **Payload:** ```json { "sub": "user-uuid", "email": "luna@example.com", "display_name": "Luna", "household_id": "household-uuid", "iat": 1708272600, "exp": 1708273500 } ``` #### Refresh Token - **Laufzeit:** 30 Tage - **Storage:** httpOnly Cookie, Secure, SameSite=Strict - **Rotation:** Neuer Refresh Token bei jedem Access Token Refresh - **Cookie Name:** `luna_refresh_token` ### 4.2 Auto-Refresh Flow ```typescript // Auth Context implementiert Auto-Refresh const authContext = { // Access Token im Memory accessToken: string | null, // User Info aus Token dekodiert user: User | null, // Auto-refresh 2 Minuten vor Ablauf refreshToken: () => Promise, logout: () => void } // Axios Interceptor für Auto-Refresh axios.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401) { await authContext.refreshToken() // Retry original request return axios.request(error.config) } return Promise.reject(error) } ) ``` ### 4.3 Route Protection ```typescript // Protected Route Component function ProtectedRoute({ children }: { children: React.ReactNode }) { const { user, loading } = useAuth() if (loading) return if (!user) return return <>{children} } // App Router } /> } /> } /> ``` ### 4.4 Login Flow States 1. **Unauthenticated:** Redirect zu `/login` 2. **Login Success:** - Set Access Token in Memory - Set Refresh Token in Cookie - Redirect zu ursprünglicher Route oder `/` 3. **Token Expired:** Auto-refresh, bei Fehler → Logout 4. **Logout:** Clear Token + Cookie, Redirect zu `/login` ## 5. Haushalt-Feature ### 5.1 Haushalt erstellen 1. User klickt "Haushalt erstellen" in `/profile/household` 2. Modal: Haushalt-Name eingeben 3. Backend erstellt Haushalt + generiert Einladungscode 4. User wird automatisch als Owner hinzugefügt 5. Einladungscode wird angezeigt zum Teilen ### 5.2 Haushalt beitreten #### Flow 1. User erhält Einladungscode (per Link, QR-Code oder manuell) 2. In `/profile/household` → Button "Haushalt beitreten" 3. **Eingabe:** 6-stelliger Code (uppercase, alphanumerisch, z.B. `COOK2A`) 4. Backend validiert Code → fügt User als `member` hinzu 5. Erfolg: Konfetti 🎉 + Toast "Willkommen bei [Haushalt-Name]!" 6. Einkaufsliste zeigt ab sofort Haushalt-Items #### Deep Link Support - URL-Format: `https://luna.supertoll.xyz/join/{invite_code}` - Wenn eingeloggt → direkt beitreten (mit Bestätigungs-Dialog) - Wenn nicht eingeloggt → Redirect zu `/login?redirect=/join/{code}` - Nach Login → automatisch Join-Flow fortsetzen #### QR-Code - Owner kann QR-Code generieren (enthält Deep Link) - Share-Button: QR als Bild teilen oder Code kopieren - Library: `qrcode.react` (lightweight) #### Fehlerbehandlung - `INVALID_INVITE_CODE` → "Code ungültig oder abgelaufen" - `ALREADY_IN_HOUSEHOLD` → "Du bist bereits in einem Haushalt. Zuerst verlassen?" - `HOUSEHOLD_FULL` → "Haushalt hat maximale Mitgliederzahl erreicht" (Limit: 10) - Netzwerkfehler → Retry-Button #### Haushalt verlassen - Member können jederzeit verlassen (Bestätigungs-Dialog) - Owner kann nur verlassen wenn ein anderer Member zum Owner gemacht wird - Letzter Member → Haushalt wird gelöscht - Nach Verlassen: eigene Shopping-Items bleiben privat erhalten ### 5.3 Gemeinsame Einkaufsliste #### Datenlogik - Shopping Items mit `household_id` sind für alle Haushaltsmitglieder sichtbar - Shopping Items mit nur `user_id` sind privat - Standard: Neue Items werden Haushalt zugeordnet (wenn User in Haushalt ist) - Toggle: "Privat" Checkbox beim Hinzufügen #### UI/UX - **Indicator:** Haushalt-Items haben kleines Haushalt-Icon - **Filter:** "Alle", "Haushalt", "Privat" Tabs in Shopping Liste - **Mobile:** Swipe-Actions: "Als privat markieren" / "Mit Haushalt teilen" ### 5.4 Smarte Mengenabfrage beim Rezept-Einkauf Aktuell werden beim "Zur Einkaufsliste hinzufügen" alle Zutaten 1:1 übernommen. Aber oft hat man schon Mehl, Eier, Zucker etc. daheim. Statt blind alles draufzupacken → User fragen was noch fehlt. #### Flow: "Was brauchst du noch?" 1. User klickt "Zutaten zur Einkaufsliste" auf der Rezeptseite 2. **Modal/Sheet öffnet sich** mit allen Zutaten als Checkliste 3. Jede Zutat hat: - ☑️ Checkbox (Standard: alle an) - Name + Menge aus Rezept - Optional: Menge anpassen (z.B. "hab noch 200g, brauch nur 300g") 4. User deaktiviert was schon da ist 5. Button: "X Artikel hinzufügen" (zeigt Anzahl der aktiven) 6. Nur ausgewählte Zutaten landen auf der Liste #### Quick-Actions im Modal - **"Alles"** — alle Checkboxen an (default) - **"Basics abwählen"** — typische Vorrats-Zutaten automatisch deaktivieren (Salz, Pfeffer, Öl, Wasser) - **"Nichts"** — alle aus, manuell auswählen #### Basics-Liste (konfigurierbar pro User/Haushalt) Standard-Basics die man meistens daheim hat: - Salz, Pfeffer, Zucker, Mehl, Öl, Wasser, Butter, Eier - User kann in Settings eigene Basics definieren - Passt zu Luna: Mehl ✅ Zucker ✅ Eier ✅ Backpulver ✅ (Butter ❌ muss immer drauf 😄) #### Späterer Ausbau (v2+: Vorratskammer/Pantry) - Automatischer Abgleich mit Vorratskammer - "Hab ich schon" wird automatisch vorausgewählt - Fehlmengen werden berechnet (Rezept braucht 500g, hast 200g → 300g auf Liste) ### 5.5 Manuelle Artikel zur Einkaufsliste hinzufügen Aktuell kommen Shopping-Items nur aus Rezepten. User sollen auch eigene Artikel hinzufügen können (z.B. "Klopapier", "Spülmittel", Zutaten ohne Rezept). #### API ##### POST /api/shopping/manual ```json // Request { "name": "Klopapier", "amount": "1", "unit": "Packung", "private": false } // Response (201 Created) { "success": true, "item": { "id": "uuid", "name": "Klopapier", "amount": "1", "unit": "Packung", "checked": false, "recipe_id": null, "user_id": "user-uuid", "household_id": "household-uuid", "created_at": "2026-02-18T16:00:00Z" } } ``` #### DB-Erweiterung ```sql -- recipe_id wird NULLABLE (ist es vermutlich schon) -- Manuelle Items haben recipe_id = NULL -- Optional: source-Feld um Herkunft zu unterscheiden ALTER TABLE shopping_items ADD COLUMN source TEXT DEFAULT 'recipe' CHECK (source IN ('recipe', 'manual')); ``` #### Frontend UI ##### Eingabefeld (oben in der Einkaufsliste) - **Sticky Input-Bar** oben auf der Shopping-Page - Textfeld mit Placeholder "Artikel hinzufügen..." + ➕ Button - **Smart Parsing:** "2 Liter Milch" → amount: "2", unit: "Liter", name: "Milch" - **Autocomplete:** Vorschläge aus bisherigen Items (häufig gekaufte) - Enter oder ➕ fügt hinzu, Feld wird geleert - Standard: Haushalt-Item (wenn in Haushalt), sonst privat ##### Smart Parsing Regeln ``` "Milch" → name: "Milch", amount: null, unit: null "2 Milch" → name: "Milch", amount: "2", unit: null "2 Liter Milch" → name: "Milch", amount: "2", unit: "Liter" "500g Mehl" → name: "Mehl", amount: "500", unit: "g" "1 Packung Butter" → name: "Butter", amount: "1", unit: "Packung" ``` ##### Visuelle Unterscheidung - Rezept-Items: normaler Style + Rezept-Name als Subtitle - Manuelle Items: leicht anderer Style (z.B. 📝 Icon oder kursiver Subtitle "Manuell hinzugefügt") - Haushalt-Items: 🏠 Badge - Private Items: 🔒 Badge ##### Quick-Add Vorschläge - Unter dem Input: Chips mit häufig hinzugefügten Artikeln - Max 5 Vorschläge, basierend auf History - Tap = sofort hinzufügen ### 5.4 Persönliche Favoriten - Favoriten sind immer pro User (`user_favorites` Tabelle) - Keine Sharing-Option für Favoriten in v2 - Favoriten-Liste zeigt nur eigene Favoriten ## 6. Migration Strategy ### 6.1 Backwards Compatibility - **Anonymous Access:** Rezepte bleiben öffentlich zugänglich ohne Login - **URLs:** Alle bestehenden Recipe-URLs funktionieren weiterhin - **Features:** Basis-Funktionen (Rezepte suchen, ansehen) ohne Auth ### 6.2 Daten-Migration #### Phase 1: Schema Updates ```sql -- Neue Tabellen erstellen (siehe Datenmodell) -- Bestehende Tabellen erweitern (users_id Spalten als optional) -- Default User erstellen für Migration INSERT INTO users (id, email, display_name, password_hash) VALUES ( 'migration-user-uuid', 'migration@luna-recipes.local', 'Luna (Migration)', 'no-login' ); ``` #### Phase 2: Daten zuweisen ```sql -- Bestehende Rezepte dem Migration User zuweisen UPDATE recipes SET created_by = 'migration-user-uuid' WHERE created_by IS NULL; -- Bestehende Notes dem Migration User zuweisen UPDATE notes SET user_id = 'migration-user-uuid' WHERE user_id IS NULL; -- Shopping Items vorerst ohne user_id lassen -- Werden beim ersten User-Login zugewiesen ``` #### Phase 3: Erste echte User - Erste User die sich registrieren bekommen alle bestehenden Daten zugewiesen - Migration User wird nach erstem echten User gelöscht - Shopping Items ohne user_id werden beim Login zugewiesen ### 6.3 Rollback Plan - Auth-Features sind additiv, keine Breaking Changes - Bei Problemen: Auth-Routes deaktivieren, App läuft weiter ohne Auth - Datenbank-Rollback: user_id Spalten auf NULL setzen ## 7. Sicherheit ### 7.1 Passwort-Sicherheit ```javascript // bcrypt mit 12 Rounds (Backend) const passwordHash = await bcrypt.hash(password, 12) // Passwort-Validierung (Frontend + Backend) const passwordRules = { minLength: 8, requireUppercase: false, // UX-freundlich für v2 requireNumbers: false, requireSpecialChars: false } ``` ### 7.2 JWT Security ```javascript // JWT Signing (Backend) const accessToken = jwt.sign( { sub: user.id, email: user.email, display_name: user.display_name, household_id: user.household_id }, process.env.JWT_SECRET, { expiresIn: '15m' } ) // httpOnly Cookie Config res.cookie('luna_refresh_token', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days }) ``` ### 7.3 Rate Limiting ```javascript // Login Rate Limiting const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts per IP message: 'Zu viele Login-Versuche, bitte warten Sie 15 Minuten', standardHeaders: true }) // Registration Rate Limiting const registerLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 3, // 3 registrations per IP per hour message: 'Zu viele Registrierungen, bitte warten Sie' }) ``` ### 7.4 Input Validation ```javascript // Zod Schema für API Validation (Backend) const registerSchema = z.object({ email: z.string().email('Ungültige E-Mail-Adresse'), password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'), display_name: z.string().min(2, 'Name muss mindestens 2 Zeichen haben') .max(50, 'Name darf maximal 50 Zeichen haben') }) // XSS Protection für User-generierte Inhalte const sanitizedDisplayName = DOMPurify.sanitize(display_name) ``` ## 8. Mobile-First UX Considerations ### 8.1 Touch Targets - **Minimum Size:** 44px × 44px für alle Buttons - **Spacing:** 8px minimum zwischen clickbaren Elementen - **Form Fields:** 48px Höhe für bessere Touch-Ergonomie ### 8.2 Responsive Breakpoints ```css /* Mobile First */ .auth-form { width: 100%; padding: 1rem; } /* Tablet */ @media (min-width: 768px) { .auth-form { max-width: 400px; margin: 0 auto; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); } } ``` ### 8.3 Keyboard & Input Handling ```jsx // Input Types für bessere Mobile Keyboards // Auto-focus Management ``` ### 8.4 Loading States - **Skeleton Loading:** Für User Profile, Haushalt-Listen - **Button States:** Loading Spinner in Buttons während API Calls - **Toast Messages:** Für Success/Error Feedback - **Optimistic Updates:** Favoriten, Haushalt beitreten ### 8.5 Offline Considerations - **Service Worker:** Caching für Auth-relevante Pages - **Network Awareness:** Retry-Mechanismus für failed Requests - **Local Storage:** Backup für kritische User Preferences ## 9. Testing Strategy ### 9.1 Unit Tests - Auth Context / Hook Tests - Password Validation Tests - JWT Token Handling Tests - API Response Validation Tests ### 9.2 Integration Tests - Login/Register Flow End-to-End - Token Refresh Flow - Haushalt erstellen/beitreten Flow - Migration Logic Tests ### 9.3 Security Tests - SQL Injection Prevention - XSS Prevention - CSRF Protection Validation - Rate Limiting Effectiveness ## 10. Performance ### 10.1 Bundle Size - **Code Splitting:** Auth-Pages lazy loaded - **Tree Shaking:** Nur genutzte Auth-Libraries - **JWT Library:** Lightweight jwt-decode statt vollständiger Library ### 10.2 API Optimization - **Response Caching:** User Profile Daten - **Debounced Requests:** Profile Updates - **Batch Requests:** Initial App Load (User + Household in einem Call) ### 10.3 Database Performance ```sql -- Wichtige Indizes für Auth Queries CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_household_members_user_id ON household_members(user_id); CREATE INDEX idx_shopping_items_user_household ON shopping_items(user_id, household_id); CREATE INDEX idx_user_favorites_user_id ON user_favorites(user_id); ``` --- ## Implementierungs-Prioritäten ### Phase 1 (MVP) 1. ✅ Datenbank Schema + Migration 2. ✅ Backend Auth Endpoints 3. ✅ Frontend Login/Register Pages 4. ✅ JWT Token Flow + Auto-Refresh 5. ✅ Route Protection ### Phase 2 (Multi-User) 1. User Profile Management 2. Shopping Items User-Zuordnung 3. Favorites pro User 4. Notes pro User ### Phase 3 (Households) 1. Household Creation + Joining 2. Shared Shopping Lists 3. Household Management UI 4. Invite Code Generation ### Phase 4 (Polish) 1. Avatar Upload 2. Advanced Security Features 3. Performance Optimizations 4. Mobile UX Improvements **Geschätzte Entwicklungszeit:** 3-4 Wochen für komplette Implementierung