Files
meldestelle/docs/99_Journal/2026-03-09_Session_Log_Keycloak_Haertung.md
T
stefan b9a433f772 feat: implement OIDC PKCE flow for Keycloak login with frontend-client
Completed OIDC Authorization Code Flow with PKCE (S256) for JS and JVM platforms.
- Added `launchOidcFlow`, `consumePendingOidcCallback`, and `getOidcRedirectUri` with platform-specific implementations.
- Integrated SHA-256 and Base64URL helpers for PKCE.
- Updated `LoginViewModel` with OIDC logic (key handling, token exchange, state validation).
- Enhanced `LoginScreen` with an OIDC login button and loading spinner.
- Verified implementation with system hardening roadmap tasks.

Includes browser redirects for JS, localhost HTTP callback for JVM, and built-in Keycloak URL construction.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
2026-03-09 15:51:42 +01:00

5.9 KiB

Journal - 2026-03-09

📝 Zusammenfassung

Keycloak-Härtung der Realm-Konfiguration (meldestelle-realm.json). Aufbauend auf dem Infrastruktur-Fix vom 2026-03-06 (korrektes start --optimized, Healthcheck) wurden nun die Client-Konfigurationen und Sicherheitseinstellungen auf einen produktionsreifen Stand gebracht.

🛠️ Änderungen

1. config/docker/keycloak/meldestelle-realm.json

Wildcard aus webOrigins entfernt

  • Vorher: api-gateway und web-app hatten "*" in webOrigins (CORS-Sicherheitslücke).
  • Nachher: Nur explizit erlaubte Origins (localhost:*, app.meldestelle.at).

Neuer Client: frontend-client (KMP Desktop & Mobile)

  • Public Client mit PKCE S256 (kein Client-Secret nötig, sicher für native Apps).
  • Redirect URIs: meldestelle://callback (Custom URI Scheme für Desktop), http://localhost:* (Dev), https://app.meldestelle.at/* (Prod).
  • Kein directAccessGrantsEnabled — verhindert Password-Grant-Flow (unsicher für native Apps).

Password-Policy gestärkt

  • Vorher: length(8) — zu schwach.
  • Nachher: length(10) and digits(1) and upperCase(1) and specialChars(1) and notUsername(undefined).

post.logout.redirect.uris konfiguriert

  • Alle relevanten Clients (api-gateway, web-app, frontend-client) haben nun korrekte Logout-Redirect-URIs gemäß OIDC Back-Channel Logout Standard.

📚 Gelerntes / Entscheidungen

  • meldestelle://callback als Custom URI Scheme: Für KMP Compose Desktop ist ein eigenes URI-Schema der sichere Standard (kein offener HTTP-Port nötig). Muss im OS registriert werden.
  • PKCE S256 ist Pflicht für Public Clients: Verhindert Authorization Code Interception Attacks. Keycloak 26.x unterstützt dies nativ.
  • frontend-client vs. web-app: Klare Trennung: web-app für Browser-basierte Web-App, frontend-client für native KMP Desktop/Mobile-App.

Micrometer Upgrade (2026-03-09, gleiche Session)

Verifiziert: micrometer = "1.16.1" und micrometerTracing = "1.6.1" waren bereits korrekt in gradle/libs.versions.toml gesetzt — kein Code-Change erforderlich. Archiv-Roadmaps entsprechend abgehakt.

Zipkin Integration (2026-03-09, gleiche Session)

Analyse ergab: Infrastruktur war bereits zu ~90% korrekt aufgebaut. Einzige Lücke war eine falsche Propagation-Konfiguration im Gateway.

Befund

  • dc-infra.yaml: Zipkin-Container (openzipkin/zipkin:3, Port 9411) bereits vorhanden.
  • monitoring-client-Bundle: enthält micrometer-tracing-bridge-brave, zipkin-reporter-brave, zipkin-sender-okhttp3 — von allen Services eingebunden.
  • monitoring-defaults.properties: setzt management.zipkin.tracing.endpoint=http://localhost:9411/api/v2/spans als Default, überschreibbar via MANAGEMENT_ZIPKIN_TRACING_ENDPOINT.
  • dc-backend.yaml: setzt MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=http://zipkin:9411/api/v2/spans für alle Services inkl. Gateway.

Fix: Gateway application.yaml

  • management.tracing.propagation.type: w3cb3 (B3 ist das native Format von Brave/Zipkin; W3C wäre für OpenTelemetry — Mismatch hätte Trace-Korrelation zwischen Gateway und Services gebrochen).
  • management.zipkin.tracing.endpoint: Explizit mit lokalem Default + ENV-Override ergänzt.
  • TRACING_SAMPLING_PROBABILITY: Konsistent mit Services via ENV-Variable.

Fix: Gateway build.gradle.kts

  • Redundante direkte micrometer-tracing-bridge-brave-Dependency entfernt (bereits transitiv via monitoring-client vorhanden).

OIDC Client im Frontend (2026-03-09, gleiche Session)

Login-Flow mit PKCE Authorization Code Flow (S256) für frontend-client implementiert.

Neue Dateien

  • frontend/core/auth/src/commonMain/.../Sha256.kt: Pure Kotlin SHA-256 (FIPS 180-4) + Base64URL-Encoding — kein expect/actual, läuft auf JVM/JS/Wasm.
  • frontend/core/auth/src/commonMain/.../PkceHelper.kt: Code Verifier, Code Challenge (S256), State Generator.
  • frontend/core/auth/src/commonMain/.../OidcCallback.kt: OidcCallbackResult sealed class + expect launchOidcFlow(), consumePendingOidcCallback(), getOidcRedirectUri().
  • frontend/core/auth/src/jvmMain/.../OidcCallback.jvm.kt: Eingebetteter HttpServer (Port 18080) + Desktop.browse() + CompletableDeferred (Timeout 5 min).
  • frontend/core/auth/src/jsMain/.../OidcCallback.js.kt: window.location.href Redirect + URL-Parameter-Parsing beim App-Start + History-Bereinigung via replaceState.

Geänderte Dateien

  • frontend/core/domain/.../AppConstants.kt: KEYCLOAK_CLIENT_IDfrontend-client, OIDC-Konstanten ergänzt.
  • frontend/core/auth/src/commonMain/.../AuthApiClient.kt: buildAuthorizationUrl() (PKCE URL-Builder) + exchangeCodeForToken() (Code → Token).
  • frontend/core/auth/src/commonMain/.../LoginViewModel.kt: isOidcLoading-State, startOidcFlow(), handleOidcCallbackResult(), JS-Init-Callback-Check.
  • frontend/core/auth/src/commonMain/.../LoginScreen.kt: Divider + OutlinedButton „Mit Keycloak anmelden" mit Spinner bei laufendem Flow.

Architektur-Entscheidungen

  • Kein ktor-client-auth: Der OIDC-Flow wird manuell implementiert — ktor-client-auth unterstützt Authorization Code + PKCE nicht nativ für KMP.
  • Pure Kotlin SHA-256: Kein expect/actual nötig — kotlin.math + reine Bitoperationen reichen aus.
  • JVM-Callback-Server auf localhost:18080: Standard-Muster für Desktop-Apps (RFC 8252 „OAuth 2.0 for Native Apps").
  • JS-Redirect-Flow: Kein Popup — volle Seitenweiterleitung. Code Verifier wird in sessionStorage gespeichert (nur aktueller Tab).
  • State-Validierung: CSRF-Schutz via State-Parameter-Vergleich im ViewModel.

🔜 Nächste Schritte

  • TLS/HTTPS — Langfristig: KC_HOSTNAME_STRICT_HTTPS=true setzen, sobald TLS eingerichtet ist.
  • Gateway CircuitBreaker — Verifizieren ob durch Spring Cloud 2025.0.1 bereits behoben.