wiki/implementation/security-migration-guide.md

8.9 KiB

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)

// 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)

// 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

// 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

// 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

// 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

// 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

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

// 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

// 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

function getCSRFToken() {
    return sessionStorage.getItem('csrf_token');
}

3.2 CSRF-Token zu allen State-changing Operations hinzufügen

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

// 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

// In User-Form-Konfigurationen
const fieldsToShow = ['username', 'email', 'enabled', 'language', 'privilege'];
// 'password' entfernt für Sicherheit

4.2 Separate Passwort-Reset-Funktion implementieren

// 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