# Security Migration Guide: Frontend UI Anpassungen ## Übersicht Dieses Dokument beschreibt die notwendigen Anpassungen für alle Frontend-UIs, um das neue Sicherheitskonzept mit httpOnly-Cookies, automatischem Token-Refresh und erweitertem CSRF-Schutz zu implementieren. ## Betroffene UIs - `frontend_agents/` (bereits implementiert) - `nyla/` - `customer-althaus/frontend/` - Alle anderen Frontend-Implementierungen --- ## 1. JWT-Token Migration: localStorage → httpOnly-Cookies ### ❌ Alte Implementierung (unsicher) ```javascript // Token in localStorage speichern localStorage.setItem('auth_token', response.access_token); // Token manuell in Headers hinzufügen function getAuthHeaders() { const headers = {}; const token = localStorage.getItem('auth_token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } return headers; } ``` ### ✅ Neue Implementierung (sicher) ```javascript // Keine Token-Speicherung im Frontend nötig // Tokens werden automatisch in httpOnly-Cookies gesetzt function getAuthHeaders() { const headers = {}; // Browser sendet Cookies automatisch mit credentials: 'include' return headers; } ``` ### Anpassungen in allen UIs: #### 1.1 Login-Funktionen anpassen ```javascript // Vorher: export async function login(username, password) { const response = await api.login(username, password); localStorage.setItem('auth_token', response.access_token); return response.access_token; } // Nachher: export async function login(username, password) { const response = await api.login(username, password); // Tokens werden automatisch in httpOnly-Cookies gesetzt if (response.type === 'local_auth_success') { if (response.authenticationAuthority) { localStorage.setItem('auth_authority', response.authenticationAuthority); } return response; } throw new Error('Login failed'); } ``` #### 1.2 API-Calls anpassen ```javascript // Alle fetch()-Calls müssen credentials: 'include' verwenden const response = await fetch(url, { method: 'POST', credentials: 'include', // WICHTIG: Für Cookie-Übertragung headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); ``` #### 1.3 Auth-Check anpassen ```javascript // Vorher: export function checkAuth() { const token = localStorage.getItem('auth_token'); return !!token; } // Nachher: export function checkAuth() { // Mit httpOnly-Cookies können wir Token-Existenz nicht direkt prüfen // Wir verlassen uns auf API-Calls zur Authentifizierung if (!window.location.pathname.includes('login.html') && !window.location.pathname.includes('register.html')) { api.getCurrentUser().then(() => { return true; }).catch(() => { window.location.href = 'login.html?sessionExpired=true'; return false; }); } return true; } ``` #### 1.4 Logout anpassen ```javascript // Vorher: export async function logout() { localStorage.removeItem('auth_token'); window.location.href = 'login.html'; } // Nachher: export async function logout() { try { await api.logoutLocal(); // Server löscht Cookies } catch (e) { // Ignore errors } finally { localStorage.removeItem('auth_authority'); window.location.href = 'login.html?logout=true'; } } ``` --- ## 2. Automatischer Token-Refresh implementieren ### 2.1 Response-Handler erweitern ```javascript async function handleResponse(response, originalUrl = null, originalOptions = null) { if (!response.ok) { // Handle 401 Unauthorized with automatic token refresh if (response.status === 401 && !window.location.pathname.includes('login.html')) { try { // Attempt to refresh token await api.refreshToken(); // Retry original request if we have the details if (originalUrl && originalOptions) { const retryResponse = await fetch(originalUrl, originalOptions); return await handleResponse(retryResponse); } } catch (refreshError) { // Clear any stored tokens and redirect to login localStorage.removeItem('auth_authority'); window.location.href = '/login.html?sessionExpired=true'; return; } } // Handle other errors... const errorText = await response.text(); throw new Error(`API request failed: ${response.status} ${response.statusText}`); } return await response.json(); } ``` ### 2.2 API-Calls für Retry vorbereiten ```javascript // Alle API-Methoden müssen URL und Options für Retry speichern async function post(url, data) { const fullUrl = `${apiBasicUrl}${url}`; const options = { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }; try { const response = await fetch(fullUrl, options); return await handleResponse(response, fullUrl, options); } catch (error) { throw error; } } ``` ### 2.3 Refresh-Token-Endpoint hinzufügen ```javascript // In apiCalls.js oder ähnlicher API-Datei const api = { // ... andere Methoden refreshToken: async function() { try { return await privateApi.post('/api/local/refresh', {}); } catch (error) { throw error; } } }; ``` --- ## 3. CSRF-Schutz erweitern ### 3.1 CSRF-Token-Funktion hinzufügen ```javascript function getCSRFToken() { return sessionStorage.getItem('csrf_token'); } ``` ### 3.2 CSRF-Token zu allen State-changing Operations hinzufügen ```javascript async function post(url, data) { const headers = { 'Content-Type': 'application/json' }; // Add CSRF token for state-changing operations const csrfToken = getCSRFToken(); if (csrfToken) { headers['X-CSRF-Token'] = csrfToken; } const options = { method: 'POST', credentials: 'include', headers: headers, body: JSON.stringify(data) }; // ... rest of implementation } ``` ### 3.3 CSRF-Token-Generierung in Login-Seiten ```javascript // In login.html oder ähnlichen Seiten function generateCSRFToken() { const array = new Uint32Array(8); window.crypto.getRandomValues(array); return Array.from(array, dec => ('0' + dec.toString(16)).slice(-2)).join(''); } // CSRF Token setzen const csrfToken = generateCSRFToken(); sessionStorage.setItem('csrf_token', csrfToken); ``` --- ## 4. Passwort-Sicherheit verbessern ### 4.1 Passwort-Felder aus Admin-Forms entfernen ```javascript // In User-Form-Konfigurationen const fieldsToShow = ['username', 'email', 'enabled', 'language', 'privilege']; // 'password' entfernt für Sicherheit ``` ### 4.2 Separate Passwort-Reset-Funktion implementieren ```javascript // Neue API-Methode hinzufügen resetUserPassword: async function(userId, newPassword) { return await privateApi.post(`/api/users/${userId}/reset-password`, { newPassword }); }, // Passwort-Reset-UI implementieren async function resetUserPassword(userId) { const newPassword = prompt('Neues Passwort eingeben (mindestens 8 Zeichen):'); if (!newPassword || newPassword.length < 8) { alert('Passwort muss mindestens 8 Zeichen lang sein.'); return; } try { await api.resetUserPassword(userId, newPassword); alert('Passwort erfolgreich zurückgesetzt.'); } catch (error) { alert('Fehler: ' + error.message); } } ``` --- ## 5. Datei-spezifische Anpassungen ### 5.1 API-Calls-Datei (z.B. `apiCalls.js`) - `getAuthHeaders()` vereinfachen (keine localStorage-Zugriffe) - `getCSRFToken()` hinzufügen - Alle HTTP-Methoden für Retry vorbereiten - `refreshToken()` Methode hinzufügen ### 5.2 Auth-Datei (z.B. `auth.js`) - `login()` Funktion anpassen (keine Token-Speicherung) - `checkAuth()` Funktion anpassen (API-basierte Prüfung) - `logout()` Funktion anpassen (Cookie-Clearing) ### 5.3 Form-Module (z.B. `formUsers.js`) - Passwort-Felder aus `fieldsToShow` entfernen - Passwort-Reset-Funktionalität hinzufügen ### 5.4 HTML-Seiten - CSRF-Token-Generierung hinzufügen - Session-Storage für CSRF-Token einrichten --- ## 6. Testing-Checkliste ### 6.1 Funktionalitätstests - [ ] Login funktioniert mit httpOnly-Cookies - [ ] Automatischer Token-Refresh bei 401-Fehlern - [ ] Logout löscht alle Cookies - [ ] CSRF-Token werden bei POST/PUT/DELETE übertragen - [ ] Passwort-Reset funktioniert für Admins ### 6.2 Sicherheitstests - [ ] Tokens sind nicht in localStorage sichtbar - [ ] Tokens sind nicht über JavaScript zugänglich - [ ] CSRF-Angriffe werden blockiert - [ ] Passwörter werden nicht im Frontend übertragen ### 6.3 Browser-Kompatibilität - [ ] Chrome/Edge (Chromium) - [ ] Firefox - [ ] Safari - [ ] Mobile Browser ---