From cb22fff40709792f9858e11987b55bd7e22b7215 Mon Sep 17 00:00:00 2001 From: Dennis Nemec Date: Fri, 15 May 2026 11:16:18 +0200 Subject: [PATCH] Phase B Fixes: AppAuth-Stored-State + LAN-Cleartext + force prompt=login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drei zusammenhängende Korrekturen, die den OIDC-Flow auf realen Geräten durchgehen lassen: 1. taskAffinity="" raus aus MainActivity — sonst landet die RedirectUriReceiverActivity beim Rücksprung aus Samsung Internet Custom Tabs (FLAG_ACTIVITY_NEW_TASK) in einem zweiten Task und zweitem App-Prozess, AppAuth findet seinen in-memory PKCE-State nicht und meldet 'No stored state - unable to handle response'. 2. network_security_config.xml: base-config cleartextTrafficPermitted statt einzelner localhost/10.0.2.2-Domains. Notwendig für Tests gegen die LAN-IP des Dev-Macs (z.B. 192.168.x.x); AndroidConfig kann keine IP-Wildcards. Klar als Dev-only markiert. 3. promptValues=['login'] auf der AuthorizationTokenRequest — verhindert den Instant-SSO-Cookie-Redirect-Race, bei dem Chrome Custom Tabs schliesst, bevor der Redirect-Intent ankommt; AppAuth wuerde sonst 'User cancelled flow' melden, obwohl der Nutzer nicht abgebrochen hat. UX-mässig auch gewollt: jeder Login frisch (Account-Wechsel am gleichen Geraet ist denkbar), Restore laeuft über den Refresh- Token aus der Secure Storage. --- android/app/src/main/AndroidManifest.xml | 27 ++++++++++++------- .../main/res/xml/network_security_config.xml | 26 ++++++++++-------- .../network/keycloak_oidc_token_provider.dart | 8 ++++++ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1701ed1..44c16c1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,11 +23,20 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" - android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> + diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml index f1aab2a..2d1ea8e 100644 --- a/android/app/src/main/res/xml/network_security_config.xml +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -1,17 +1,21 @@ - - 10.0.2.2 - localhost - 127.0.0.1 - + + + + + diff --git a/lib/data/network/keycloak_oidc_token_provider.dart b/lib/data/network/keycloak_oidc_token_provider.dart index e46eef3..9227c13 100644 --- a/lib/data/network/keycloak_oidc_token_provider.dart +++ b/lib/data/network/keycloak_oidc_token_provider.dart @@ -84,6 +84,14 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider { // würde sonst abbrechen. In Produktion (HTTPS) ist das ein // No-Op und kann bleiben. allowInsecureConnections: true, + // Wichtig auf Android: ohne `prompt=login` würde Keycloak bei + // bestehender SSO-Session sofort 302 nach holzleitner://... + // antworten, Chrome schließt den Custom Tab dabei so schnell, + // dass der Redirect-Intent unsere RedirectUriReceiverActivity + // gar nicht erreicht — AppAuth meldet stattdessen + // "User cancelled flow". Erzwingen der Login-Maske → echter + // User-Click → sauberer Intent-Dispatch. + promptValues: const ['login'], ), );