1
0
Files
breadcrumb-the-shire/docs/explanation-session-management.md
fs 545bcc789b docs: replace ASCII umlaut fallbacks with proper äöüß across all .md
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>
2026-04-29 09:57:22 +02:00

184 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`