# Session-Management Letzte Aktualisierung: 2026-03-14 ## Ziel Vollstaendige Erklaerung des Session-Lifecycle: Wann wird ein User abgemeldet, wann greift der Remember-Token, wie bleiben Frontend und Server synchron. ## Session-Lifecycle (Uebersicht) ``` Login (Passwort / SSO) │ ├─ $_SESSION['session_started_at'] = time() ├─ $_SESSION['session_last_activity'] = time() └─ Optional: Remember-Token (Cookie, 30 Tage) │ ▼ Jeder Request (web/index.php) │ ├─ Session vorhanden? │ ├─ JA → Idle-Check + Absolute-Check │ └─ NEIN → Remember-Cookie vorhanden? │ ├─ JA → Auto-Login (Token validieren + rotieren) │ └─ NEIN → Redirect /login │ ├─ authz_version geaendert? → Permissions neu laden └─ User deaktiviert/geloescht? → Forced Logout ``` ## Zwei Timeouts | Timeout | Default | Bereich | Zuruecksetzbar? | DB-Key | |---------|---------|---------|-----------------|--------| | Idle | 30 Min | 5 Min – 24 Std | Ja, bei jeder Aktivitaet | `session_idle_timeout_minutes` | | Absolute | 8 Std | 1 Std – 72 Std | Nein, nie | `session_absolute_timeout_hours` | Pruefung in `web/index.php` (bei jedem Request): ``` Idle: (now - last_activity) > idle_timeout → Timeout Absolute: (now - session_started_at) > absolute_timeout → Timeout ``` Einer greift → `logoutDueToSessionTimeout()` → Audit-Event `auth.session.timeout` → Flash-Message → Redirect `/login?return_to=`. Wichtig: Der Absolute-Timeout wird nie zurueckgesetzt, auch nicht durch "Session verlaengern". Er begrenzt die maximale Sitzungsdauer hart. ## Frontend-Warning und Keepalive Quelle: `web/js/components/app-session-warning.js` ``` 0 Min 28 Min 30 Min ├───────────────┤───────────────┤ Aktiv Warning-Dialog Timeout (2 Min Countdown) ``` ### Interaktions-Tracking - Events: `click`, `keydown`, `scroll`, `mousemove`, `touchstart` - Throttled auf 30s (kein Overhead bei schnellem Scrollen) - Setzt nur den JS-Timer zurueck, nicht den Server-Timer ### Background-Keepalive Problem ohne Keepalive: User scrollt 29 Min auf einer Seite → JS zeigt "aktiv", Server sagt "idle". Loesung: Alle 5 Minuten prueft ein Intervall ob seit dem letzten Sync Interaktion stattfand. Falls ja → `POST /admin/session-ping/data` → Server-Timer zurueckgesetzt. | Szenario | Keepalive-Ping? | |----------|-----------------| | User aktiv (Maus, Scroll, Klick) | Ja, alle 5 Min | | User wirklich idle | Nein | | Warning-Dialog offen | Nein | | Tab im Hintergrund | Nein (keine Events) | ### Warning-Dialog - Erscheint 2 Min vor Idle-Timeout - Nur bei Idle-Timeout >= 3 Min - Kann nicht per ESC oder Backdrop geschlossen werden - Zwei Optionen: **Session verlaengern** (POST Ping) oder **Abmelden** (Redirect /logout) ## Remember-Token Quelle: `core/Service/Auth/RememberMeService.php` ### Token-Struktur ``` Cookie "remember" = selector:token (24 hex) (64 hex) DB: user_remember_tokens ├─ selector → Lookup (UNIQUE Index) ├─ token_hash → SHA-256 (constant-time Verify) ├─ expires_at → Default 30 Tage (konfigurierbar 1-365) └─ last_used ``` Selector und Token sind getrennt: Selector fuer schnellen DB-Lookup, Token-Hash fuer timing-sichere Validierung. Kein Klartext-Token in der DB. ### Auto-Login-Ablauf 1. Kein aktiver Session-User → Remember-Cookie lesen 2. Rate-Limit pruefen (IP nicht blockiert?) 3. Selector → DB-Lookup 4. Token → `hash_equals(SHA256)` Verify 5. User aktiv? Token nicht abgelaufen? 6. Session neu aufbauen (User, Permissions, Tenant-Kontext) 7. **Token rotieren** (neuer Hash, neues Expiry) 8. Rate-Limit-Counter zuruecksetzen Token-Rotation bei jedem Auto-Login begrenzt das Fenster bei Cookie-Diebstahl. ### Rate Limiting (Remember-Token) Quelle: `core/Service/Auth/RememberMeRateLimiter.php` - Storage: Memcached (`Cache::add` + `Cache::increment`) - Limit: 5 Fehlversuche pro IP in 15 Minuten - Counter laeuft automatisch ab (Memcached TTL) - Fail-open: Bei Memcached-Ausfall wird nicht blockiert - Erfolgreicher Auto-Login setzt Counter zurueck Siehe `/docs/howto-anfragelimits.md` fuer alle Rate-Limits. ## Zusammenspiel Absolute Timeout + Remember-Token ``` Login → 8h Absolute Timeout → Session endet │ Remember-Token? ├─ JA → Neue Session (neuer 8h-Timer!) └─ NEIN → Login-Seite ``` Solange der Remember-Token lebt (Default 30 Tage), bekommt der User nach Ablauf des Absolute-Timeout automatisch eine neue Session. Der Uebergang ist nahtlos (kein Flash, kein Login-Screen). Das bedeutet: Mit aktivem Remember-Token ist die effektive Session-Dauer nicht 8 Stunden, sondern bis zu 30 Tage. Das ist by-design, sollte aber bei Compliance-Anforderungen (z. B. echte Re-Authentifizierung nach X Stunden) beruecksichtigt werden. ## Cookie-Attribute | Attribut | Wert | Grund | |----------|------|-------| | `httponly` | `true` | Kein JS-Zugriff (XSS-Schutz) | | `samesite` | `Lax` | CSRF-Schutz bei Cross-Site-Navigation | | `secure` | dynamisch | `true` bei HTTPS, `false` bei HTTP (Dev) | | `path` | `/` | Gueltig fuer gesamte Domain | Das Secure-Flag wird automatisch anhand des Protokolls gesetzt (`RequestRuntime::isSecure()`). In Produktion muss HTTPS ueber Nginx erzwungen werden (Redirect 80→443 + HSTS). ## Audit-Events | Event | Ausloeser | |-------|-----------| | `auth.login.success` | Erfolgreicher Login (Passwort/SSO) | | `auth.login.failed` | Fehlgeschlagener Login | | `auth.logout` | Manueller Logout | | `auth.session.timeout` | Idle- oder Absolute-Timeout | ## Beteiligte Dateien | Komponente | Pfad | |------------|------| | Timeout-Pruefung | `web/index.php` | | AuthService | `core/Service/Auth/AuthService.php` | | RememberMeService | `core/Service/Auth/RememberMeService.php` | | RememberMeRateLimiter | `core/Service/Auth/RememberMeRateLimiter.php` | | Session-Settings | `core/Service/Settings/SettingsSessionGateway.php` | | Frontend-Warning | `web/js/components/app-session-warning.js` | | Session-Ping | `pages/admin/session-ping/data().php` | | Login-Action | `pages/auth/login().php` | | SSO-Callback | `pages/auth/microsoft/callback().php` | ## Vertiefung - `/docs/explanation-sicherheitsmodell.md` - `/docs/howto-anfragelimits.md` - `/docs/howto-auth-sso-smoke-test.md` - `/docs/reference-konfiguration.md`