8.9 KiB
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
fieldsToShowentfernen - 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