Inhaltsverzeichnis
3. Gateway.Service — Auth-Erweiterungen für WvdS.Shell
Stand: 2026-03-05
Übergeordnet: Auth-Architektur — Gesamtübersicht Verwandt: 2. WvdS.Shell | 4. Datenschicht | 5. Migration
Ausgangssituation
Das Gateway.Service existierte bereits als HTTP-REST-Gateway mit einer vollständigen Middleware-Pipeline — Logging, Rate-Limiting, HTTPS-Enforcement, Routing. Was fehlte, war ein vollständiges Auth-Subsystem für interaktive Benutzer. Die vorhandenen Modi (Basic, ApiKey) waren für Dienst-zu-Dienst-Kommunikation ausgelegt, nicht für Session-Management mit Token-Lifecycle und Mehrfaktor-Authentifizierung.
Ein Gateway ohne vollständiges Auth-Subsystem bedeutet: Entweder werden Credentials bei jedem Request übertragen — ein fundamentales Sicherheitsproblem — oder der Client muss selbst den Zustand verwalten, was die Architektur unnötig verkompliziert. JWT-Tokens lösen das: Ein einmaliger Auth-Vorgang erzeugt ein kurzlebiges Access Token und ein langlebiges Refresh Token. Alle nachfolgenden Requests sind stateless und tragen nur den signierten Token — kein Passwort, kein serverseitiger Session-State.
Die Wahl von ML-DSA-65 für die JWT-Signierung ist dabei keine akademische Entscheidung. Klassische Signaturverfahren (RSA, ECDSA) könnten durch Quantencomputer kompromittiert werden. Mit ML-DSA-65 als post-quanten-sicherem Verfahren bleibt die Plattform auch dann vertrauenswürdig, wenn klassische Kryptografie in absehbarer Zeit nicht mehr ausreicht — ohne dass bestehende Token-Strukturen nachträglich geändert werden müssten.
Ausgangslage
Gateway.Service hat eine vollständige Middleware-Pipeline und ein Auth-Enum mit allen benötigten Modi — jedoch ist nur ein Teil davon implementiert.
Aktuell implementiert
| Auth-Modus | Status | Anmerkung |
|---|---|---|
amNone | ✓ Aktiv | Nur für DEV ($IFDEF DEBUG) |
amBasic | ✓ Aktiv | ConstantTimeEqual, CWE-208 |
amApiKey | ✓ Aktiv | ConstantTimeEqual, Header konfigurierbar |
amBearer | — Reserviert | Interface vorhanden, nicht implementiert |
amCertificate | — Reserviert | Interface vorhanden, nicht implementiert |
amWindows | — Reserviert | Interface vorhanden, nicht implementiert |
Benötigt für WvdS.Shell
Für die Shell-Integration müssen folgende Komponenten neu implementiert werden:
- PFX Install-ID Validierung (
amCertificate) - Kerberos/SSPI Handler (
amWindows) - JWT Ausstellung und Validierung (
amBearer) - TOTP Service (neu — für externen MFA-Flow)
- Refresh Token Management (neu)
- Feature-Flags Endpoint (neu — ENIVERSASYS-Anbindung)
Neue Endpoints
Alle neuen Auth-Endpoints unter /api/v1/auth/.
Ein neuer Controller WvdS.Data.Gateway.Controller.Auth wird benötigt.
Übersicht
| Verb | Route | Auth erforderlich | Beschreibung | Warum so gestaltet? |
|---|---|---|---|---|
| POST | /api/v1/auth/kerberos | Nein (ist der Auth-Schritt selbst) | Kerberos Negotiate-Handshake | Der Handshake selbst ist der Auth-Vorgang — ein Token als Voraussetzung wäre ein Henne-Ei-Problem. |
| POST | /api/v1/auth/external | Nein | Username/Passwort → TOTP-Challenge | Kein direktes Token bei Passwort allein: Erst nach TOTP-Bestätigung gilt die Identität als gesichert. |
| POST | /api/v1/auth/totp/verify | Nein (Challenge-ID statt Token) | TOTP-Code einlösen → JWT | Die Challenge-ID ist temporär und wertlos ohne den passenden Code — kein Missbrauch ohne gültigen TOTP. |
| POST | /api/v1/auth/token/refresh | Refresh Token | Access Token erneuern | Silent Refresh im Hintergrund — der Benutzer erlebt keinen Logout, solange er aktiv ist. |
| GET | /api/v1/auth/totp/enroll | JWT Bearer | TOTP Enrollment-URI + QR generieren | Enrollment erfordert eine laufende Session — verhindert unbefugtes Einrichten fremder TOTP-Secrets. |
| DELETE | /api/v1/auth/totp/enroll | JWT Bearer | TOTP-Enrollment zurücksetzen | Nur der Kontoinhaber selbst (oder ein Admin) kann das TOTP-Geheimnis löschen. |
| GET | /api/v1/features | JWT Bearer | Feature-Flags für aktuelle Session | Feature-Flags außerhalb des JWT: Änderungen greifen sofort ohne Token-Neuausstellung. |
POST /api/v1/auth/kerberos
Kerberos Negotiate-Handshake (mehrstufig, wie HTTP Negotiate definiert):
Request:
POST /api/v1/auth/kerberos
Authorization: Negotiate <Base64-SPNEGO-Token>
X-WvdS-Install-Id: ENIVERS-2026-001
Response (ggf. mehrstufig):
HTTP 401 Authorization: Negotiate <Base64-Challenge> ← Server-Challenge
HTTP 200 { "access_token": "...", "refresh_token": "...", "expires_in": 900 }
POST /api/v1/auth/external
Erster Schritt des externen MFA-Flows. Gibt bei gültigem Username/Passwort
eine challenge_id zurück — noch kein Token.
Request:
{ "username": "max.muster", "password": "...", "install_id": "ENIVERS-2026-001" }
Response 200:
{ "mfa_required": true, "challenge_id": "c7f3a..." }
Response 401:
{ "error": "invalid_credentials" }
Passwort wird nach Validierung sofort via FillChar zeroiert (CWE-316).
POST /api/v1/auth/totp/verify
Zweiter Schritt des externen MFA-Flows. Löst die challenge_id gegen
den TOTP-Code ein und gibt bei Erfolg die Token-Pair aus.
Request:
{ "challenge_id": "c7f3a...", "code": "482910" }
Response 200:
{ "access_token": "...", "refresh_token": "...", "expires_in": 900 }
Response 401:
{ "error": "invalid_code", "attempts_remaining": 2 }
POST /api/v1/auth/token/refresh
Silent Token Refresh. Refresh-Token wird im Request-Body oder als
Authorization: Refresh <token> übergeben.
Request:
{ "refresh_token": "..." }
Response 200:
{ "access_token": "...", "expires_in": 900 }
Response 401:
{ "error": "refresh_token_expired" }
GET /api/v1/features
Lädt die Feature-Flags für den authentifizierten User/die Installation.
Liest aktuell aus ENIVERSCAFM (auth.*-Namespace), zukünftig aus ENIVERSASYS.
Response 200:
{ "features": ["grids", "charts", "scheduling", "docking"] }
JWT — Ausstellung und Validierung
Signaturalgorithmus
JWT wird mit ML-DSA-65 signiert — die Bibliothek ist im Gateway bereits
vorhanden (WvdS.Security.Cryptography). Kein externer Abhängigkeitsbedarf.
Standard-Header: jwt
Payload-Struktur
{
"sub": "max.muster@corp.example.com",
"install_id": "ENIVERS-2026-001",
"auth_method": "windows",
"mfa": false,
"iat": 1741132800,
"exp": 1741133700,
"jti": "a3f9..."
}
Feature-Flags werden nicht im JWT mitgeführt — sie werden bei Bedarf
frisch vom /api/v1/features-Endpoint geladen. Grund: Flags können sich
ändern ohne dass ein neues Token ausgestellt werden muss.
Token-Lebensdauer
| Token | Lebensdauer | Speicherort (Client) | Begründung |
|---|---|---|---|
| Access Token | 15 Minuten | RAM (Shell SessionManager) | Ein gestohlenes Token ist nach maximal 15 Minuten wertlos — ohne Admin-Eingriff. RAM statt Datei: Kein Risiko durch Dateisystem-Analyse; Neustart löscht das Token automatisch. |
| Refresh Token | 30 Tage | DPAPI SecretStorage | Bequeme Wiederanmeldung ohne täglichen Login. DPAPI bindet das Speichergeheimnis an das Windows-Benutzerkonto — andere Benutzer desselben PCs kommen nicht an das Token. |
| TOTP Challenge | 5 Minuten | Gateway-RAM (nach Ablauf gelöscht) | Ein abgefangener 6-stelliger Code ist nach 5 Minuten wertlos. Kein Disk-Zustand: Ein Serverneustart bereinigt alle offenen Challenges automatisch. |
Validierung in Middleware
TWvdSGatewayAuthHandler wird erweitert: Bei amBearer wird der JWT-Header
extrahiert, ML-DSA-Signatur geprüft, exp-Claim validiert und install_id
gegen die PFX-Registrierung abgeglichen.
TOTP Service
Zuständigkeit
Die gesamte TOTP-Logik liegt im Gateway — die Shell liefert nur den Code (6 Ziffern). Details: 2. Shell-Doku: TOTP Aufgabenteilung.
Implementierungsdetails
Algorithmus: HMAC-SHA1 (RFC 6238) über OpenSSL (WvdS.Security.Cryptography) — kein neuer Crypto-Code nötig.
Shared Secret Speicherung:
- Pro User ein Base32-kodiertes Secret
- Gespeichert AES-256-GCM-verschlüsselt in der Gateway-Datenbank (CWE-256)
- Kein Plaintext auf Disk
Validierungsfenster: ±1 Zeitfenster (30 s × 3 = 90 s Toleranz für Uhrabweichung)
Lockout-Policy: Bestehender FailedAuthPerMinute-Alert-Counter wird genutzt.
Nach 5 Fehlversuchen: Challenge ungültig, neuer POST /api/v1/auth/external nötig.
Challenge-Lifetime: 5 Minuten. Challenges in Gateway-RAM (TDictionary<String, TChallengeEntry>), kein DB-Roundtrip.
Enrollment Flow
Admin-seitig:
GET /api/v1/auth/totp/enroll (JWT des Admin-Users)
→ { "otpauth_uri": "otpauth://totp/...", "qr_base64": "..." }
→ Admin zeigt QR dem User (oder schickt URI per E-Mail)
Self-Service (Shell öffnet WebView):
Shell navigiert zu: https://gateway/auth/enroll?token=<JWT>
→ Gateway zeigt HTML-Seite mit QR-Code
→ User scannt mit MS Authenticator
→ Enrollment-Bestätigung (User gibt ersten Code ein zum Verifizieren)
PFX Install-ID Validierung
Das Gateway prüft bei jedem Auth-Request ob die install_id aus dem
PFX registriert und aktiv ist.
Header-Konvention
X-WvdS-Install-Id: ENIVERS-2026-001
Dieser Header wird von der Shell bei jedem Request mitgesendet — nicht nur beim Auth-Request.
Validierungslogik
1. X-WvdS-Install-Id Header vorhanden? 2. install_id in Deployment-Tabelle (DB) vorhanden und aktiv? 3. Zugehöriges Ablaufdatum noch nicht überschritten? 4. → Validierung OK: weiter mit Auth-Flow 5. → Validierung FAIL: HTTP 403 "unlicensed installation"
Eine neue Tabelle (z. B. WVDS_DEPLOYMENTS) in der Gateway-Datenbank speichert:
| Spalte | Typ | Beschreibung |
|---|---|---|
INSTALL_ID | VARCHAR(64) | PK — z. B. ENIVERS-2026-001 |
TENANT_NAME | VARCHAR(256) | Anzeigename |
LICENSE_TIER | VARCHAR(32) | professional / enterprise |
VALID_UNTIL | DATE | Ablaufdatum (aus PFX synchronisiert) |
DOMAINS | VARCHAR(512) | Erlaubte AD-Domänen (Komma-getrennt) |
ACTIVE | BIT | Manuell deaktivierbar |
Kerberos/SSPI Handler
Standalone-Modus
Wenn Gateway als Windows Service läuft (nicht hinter IIS/NGINX):
AcceptSecurityContextausSecur32.dll(Windows SSPI API)- HTTP
Authorization: NegotiateHeader — mehrstufiger Handshake - Nach Erfolg: Identität via
QueryContextAttributes(SECPKG_ATTR_NAMES)lesen - → JWT für diese Identität ausstellen
FastCGI-Modus (hinter IIS)
IIS übernimmt Windows Auth vollständig:
- IIS sendet
LOGON_USERals CGI-Umgebungsvariable - Gateway-FastCGI-Adapter (
WvdS.Data.Gateway.FastCGI.Adapter) liestLOGON_USER - Kein
AcceptSecurityContextim Gateway nötig — IIS hat bereits validiert - Direkt JWT ausstellen
Empfehlung: FastCGI hinter IIS für Corpnet-Deployments.
Standalone SSPI für Self-hosted Szenarien (kleinere Installationen).
Middleware-Änderungen
Bestehende Pipeline (9 Stufen)
1. TWvdSMetricsMiddleware 2. TWvdSLoggingMiddleware 3. TWvdSHttpsEnforcementMiddleware 4. TWvdSRequestSizeMiddleware 5. TWvdSRateLimitMiddleware 6. TWvdSGatewayAuthHandler ← Erweiterung: amBearer + amWindows 7. TWvdSPayloadEncryptionMiddleware 8. TWvdSCompressionMiddleware 9. MapControllers
Geplante Erweiterungen
| Stufe | Komponente | Änderung |
|---|---|---|
| 3.5 | TWvdSInstallIdMiddleware (neu) | X-WvdS-Install-Id prüfen, vor Auth |
| 6 | TWvdSGatewayAuthHandler | amBearer: JWT ML-DSA validieren + Claims extrahieren |
| 6 | TWvdSGatewayAuthHandler | amWindows: SSPI Negotiate oder LOGON_USER (FastCGI) |
TWvdSInstallIdMiddleware greift vor der eigentlichen Auth — ein Request ohne
gültige Install-ID wird mit HTTP 403 abgelehnt, bevor Credentials geprüft werden.
Neue Units
src/
controllers/
WvdS.Data.Gateway.Controller.Auth.pas ← /api/v1/auth/*, /api/v1/features
core/
WvdS.Data.Gateway.Auth.JwtService.pas ← JWT Ausstellung (ML-DSA-65), Validierung
WvdS.Data.Gateway.Auth.TotpService.pas ← Shared Secret, Challenge, HMAC-SHA1 (OpenSSL)
WvdS.Data.Gateway.Auth.KerberosHandler.pas ← SSPI AcceptSecurityContext (Windows-only)
WvdS.Data.Gateway.Auth.CertValidator.pas ← Install-ID gegen DB prüfen
WvdS.Data.Gateway.Auth.RefreshStore.pas ← Refresh Token (gehashed, DB-backed)
middleware/
WvdS.Data.Gateway.Middleware.InstallId.pas ← X-WvdS-Install-Id Middleware
Alle neuen Units im src/core/ und src/middleware/ — passend zur
bestehenden Schichtenstruktur.
Security — CWE-Ergänzungen
Zusätzlich zu den bestehenden 22 CWEs kommen durch die Auth-Erweiterung:
| CWE | Beschreibung | Implementierung |
|---|---|---|
| CWE-287 | Improper Authentication | Kerberos-Ticket muss vollständig validiert sein (nicht nur Presence-Check) |
| CWE-290 | Token Spoofing | JWT-Signatur (ML-DSA-65) wird bei jedem Request geprüft |
| CWE-613 | Insufficient Session Expiration | Refresh Token: 30 Tage, serverseitig revokierbar |
| CWE-620 | Unverified Password Change | TOTP-Enrollment erfordert bestätigten ersten Code |
| CWE-384 | Session Fixation | jti (JWT ID) einmalig, Replay via jti-Blacklist |
Design-Entscheidungen (protokolliert)
| Thema | Entscheidung | Begründung |
|---|---|---|
| JWT-Signatur | ML-DSA-65 — Bibliothek bereits vorhanden, kein neuer Crypto-Code | Post-quanten-sicher: Klassische Signaturverfahren (RSA, ECDSA) können durch Quantencomputer gebrochen werden. ML-DSA-65 ist heute zukunftssicher — ohne spätere Architekturänderung. |
| TOTP-Crypto | HMAC-SHA1 via OpenSSL — kein neuer Crypto-Code | RFC 6238 definiert HMAC-SHA1 als TOTP-Standard. Vorhandene OpenSSL-Integration nutzen statt neuen Crypto-Code einzuführen — weniger Fehlerquellen. |
| Kerberos Standalone | Secur32.dll SSPI direkt; FastCGI-Modus bevorzugt hinter IIS | FastCGI hinter IIS: IIS übernimmt Windows Auth vollständig, das Gateway braucht keinen SSPI-Code. Standalone SSPI bleibt als Fallback für selbst-gehostete Umgebungen ohne IIS. |
| Install-ID Transport | HTTP-Header X-WvdS-Install-Id bei jedem Request | Jeder API-Aufruf trägt die Installations-ID — nicht nur der Login. Das Gateway kann eine deaktivierte Lizenz sofort bei jedem Request abweisen, nicht erst beim nächsten Login. |
| Feature-Flags | Nicht im JWT — separater Endpoint, frisch laden | Flags im JWT würden eingefroren bleiben bis zum Token-Ablauf. Frisch geladen: Lizenzänderungen oder Feature-Aktivierungen greifen ohne Token-Neuausstellung sofort. |
| Refresh Token Storage | Gehashed (SHA-256) in DB — nie im Klartext gespeichert | Ein Datenbankdump gibt keinen verwertbaren Refresh Token preis. Nur der Client kennt das Klartext-Token — das Gateway kann nur prüfen, nicht rekonstruieren. |
| TOTP Enrollment | Admin-seitig (primär) oder Self-Service WebView (BYOD) | Admin-seitig: IT kontrolliert den Prozess bei verwalteten Geräten. Self-Service: BYOD-Nutzer können eigenständig einrichten ohne IT-Eingriff — beide Wege über denselben Gateway-Enrollment-Endpoint. |