wiki/d-guides/security-migration-guide.md

335 lines
8.9 KiB
Markdown

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