335 lines
8.9 KiB
Markdown
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
|
|
|
|
---
|