# 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=<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".

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: `lib/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: `lib/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 | `lib/Service/Auth/AuthService.php` |
| RememberMeService | `lib/Service/Auth/RememberMeService.php` |
| RememberMeRateLimiter | `lib/Service/Auth/RememberMeRateLimiter.php` |
| Session-Settings | `lib/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`
