Phase B: Keycloak OIDC (PKCE) statt Cookie-Session-Login

App-Code:
- KeycloakOidcTokenProvider: PKCE-Login via flutter_appauth, Refresh via
  Refresh-Token aus flutter_secure_storage, Session-Restore beim
  App-Start, Logout.
- AuthSessionEvent als Provider→Bloc-Brücke (LoggedIn/LoggedOut/
  SessionExpired) auf einem Broadcast-Stream.
- AuthBloc komplett umgebaut: nimmt jetzt den KeycloakOidcTokenProvider
  statt UserInfoService, mappt eingehende Provider-Events auf eigene
  Zustände. Authenticated.fromClaims() liest personalnummer + Name aus
  dem ID-Token-Payload.
- LoginPage: kein Browser+Deep-Link mehr — Button feuert
  LoginRequested, der Provider übernimmt den restlichen Flow.
- network_locator: produktiver KeycloakOidcTokenProvider, doppelt
  registriert (KeycloakOidcTokenProvider für AuthBloc,
  AuthTokenProvider für Interceptor).
- Auth-State trägt zusätzlich personalnummer/displayName/email; das
  Legacy-User-Objekt + sessionId bleiben temporär drin, damit die
  alten ERPframe-Services (Phase D) noch kompilieren.

Plattform-Setup:
- Android: appAuthRedirectScheme=holzleitner in build.gradle.kts,
  NetworkSecurityConfig erlaubt HTTP zu localhost/10.0.2.2/127.0.0.1.
- iOS: holzleitner als URL-Scheme im Info.plist, ATS-Ausnahme für
  localhost (HTTP-Keycloak im Dev-Setup).

Out of scope:
- Keine echte App-Run-Smoke — kommt mit dem User-Test.
- iOS-pod-install läuft beim ersten 'flutter run ios' automatisch.
- Old ERPframe-Services bleiben aktiv und werfen ab jetzt 401 (kein
  Cookie-Session-Token mehr) — wird in Phase D entfernt.
This commit is contained in:
Dennis Nemec
2026-05-14 22:59:36 +02:00
parent 8cf4045e44
commit 6d7e58fc0f
15 changed files with 738 additions and 281 deletions

View File

@ -1,48 +1,56 @@
/// Endpoint-Konfiguration für das Rust-Backend.
///
/// Dies ist eine Übergangs-Konfiguration für Phase A der
/// Backend-Migration. Sie wird in Phase D durch eine umfassendere
/// `LocalDocuFrameConfiguration`-Ablösung ersetzt (oder die bestehende
/// Konfiguration wird erweitert).
/// Diese Übergangs-Konfiguration für die Backend-Migration wird in
/// Phase D durch eine umfassendere Konfigurations-Ablösung verfeinert
/// (Build-Time-Flavor pro Stage etc.).
///
/// **Werte für lokale Entwicklung:**
/// * iOS-Simulator + macOS-Host: `http://127.0.0.1:3000`
/// * Android-Emulator: `http://10.0.2.2:3000`
/// * Echtes Gerät im LAN: `http://<host-IP>:3000`
/// * iOS-Simulator + macOS-Host: `http://localhost:...`
/// * Android-Emulator: `http://10.0.2.2:...`
/// * Echtes Gerät im LAN: `http://<host-IP>:...`
///
/// Default ist iOS-Simulator-tauglich. Für Android-Build vor dem
/// Compile umstellen — eine Auto-Erkennung pro Platform kommt mit der
/// Phase-D-Config.
/// Default ist iOS-Simulator-tauglich; für Android-Build vor dem
/// Compile umstellen oder per Build-Flag injizieren.
class BackendConfig {
const BackendConfig({
required this.apiBaseUrl,
required this.keycloakTokenEndpoint,
required this.keycloakIssuerUrl,
required this.keycloakClientId,
required this.keycloakRedirectUrl,
});
/// Basis-URL der Rust-API (kein abschließender Slash).
final String apiBaseUrl;
/// Vollständiger Token-Endpoint des Keycloak-Realms — Format:
/// `{issuer}/protocol/openid-connect/token`.
final String keycloakTokenEndpoint;
/// Realm-Issuer ohne `/.well-known/...`-Suffix —
/// `flutter_appauth` hängt das selbst an für die Discovery.
/// Beispiel: `http://localhost:8080/realms/holzleitner`.
///
/// **Achtung:** Keycloak prägt das `iss`-Claim aus dem Hostnamen
/// dieser URL. Das Backend erwartet exakt diesen String als
/// `KEYCLOAK_ISSUER_URL`. Mismatch → 401 mit `invalid issuer`.
final String keycloakIssuerUrl;
/// Public-Client-Id, die das Backend als `audience` erwartet
/// (aktuell `holzleitner-app`).
/// Token-Endpoint des Realms — abgeleitet aus dem Issuer.
String get keycloakTokenEndpoint =>
'$keycloakIssuerUrl/protocol/openid-connect/token';
/// Public-Client-Id (entspricht der `aud` im Backend-Token).
final String keycloakClientId;
/// Custom-Scheme-Redirect, das in Keycloak als
/// `holzleitner://oauth2redirect` whitelisted ist. Muss mit dem
/// `appAuthRedirectScheme` in `android/app/build.gradle.kts` und
/// dem `CFBundleURLSchemes`-Eintrag in `ios/Runner/Info.plist`
/// matchen.
final String keycloakRedirectUrl;
/// Default-Konfiguration für lokale Entwicklung gegen das
/// Docker-Compose-Setup (Postgres + Keycloak + Backend).
///
/// **Achtung Hostname:** Keycloak prägt das `iss`-Claim des Tokens
/// aus dem Hostnamen der Token-Endpoint-URL. Das Backend erwartet
/// `iss = http://localhost:8080/realms/holzleitner`, deshalb hier
/// `localhost` statt `127.0.0.1`. Auf Android-Emulator entsprechend
/// `10.0.2.2` setzen.
static const BackendConfig localDev = BackendConfig(
apiBaseUrl: 'http://localhost:3000',
keycloakTokenEndpoint:
'http://localhost:8080/realms/holzleitner/protocol/openid-connect/token',
keycloakIssuerUrl: 'http://localhost:8080/realms/holzleitner',
keycloakClientId: 'holzleitner-app',
keycloakRedirectUrl: 'holzleitner://oauth2redirect',
);
}