forked from fa/breadcrumb-the-shire
Most German docs were written with `ue`/`ae`/`oe`/`ss` ASCII fallbacks (`fuer`, `aenderung`, `koennen`, `gemaess`) — relic from older toolchain constraints. Replaced 237 occurrences across 38 .md files with proper umlauts and ß so the docs read naturally. Substitutions are constrained to plain prose: fenced code blocks (```...```), inline code spans (`...`), markdown link targets `[..](..)`, and HTML comments are preserved verbatim. Path references like `docs/howto-erste-aenderung.md` keep their original (umlaut-replaced) filenames — only the surrounding prose is normalised. Tool: bin/fix-md-umlauts.py (idempotent — no-op on already-clean files). Re-runnable if ASCII fallbacks creep back in via copy-paste. Doc-link-check + doc-drift-check stay green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
184 lines
6.5 KiB
Markdown
184 lines
6.5 KiB
Markdown
# 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 (Übersicht)
|
||
|
||
```
|
||
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` |
|
||
|
||
Prüfung 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=<url>`.
|
||
|
||
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".
|
||
|
||
Lösung: Alle 5 Minuten prüft 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 für schnellen DB-Lookup, Token-Hash für timing-sichere Validierung. Kein Klartext-Token in der DB.
|
||
|
||
### Auto-Login-Ablauf
|
||
|
||
1. Kein aktiver Session-User → Remember-Cookie lesen
|
||
2. Rate-Limit prüfen (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` für 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 Übergang 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 für gesamte Domain |
|
||
|
||
Das Secure-Flag wird automatisch anhand des Protokolls gesetzt (`RequestRuntime::isSecure()`). In Produktion muss HTTPS über 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-Prüfung | `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`
|