Phase B Fixes: AppAuth-Stored-State + LAN-Cleartext + force prompt=login
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.
This commit is contained in:
@ -23,11 +23,20 @@
|
|||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!--
|
||||||
|
taskAffinity bewusst NICHT auf "" gesetzt (war Flutter-
|
||||||
|
Default-Scaffold). Grund: flutter_appauth's
|
||||||
|
RedirectUriReceiverActivity erbt die Default-Affinität
|
||||||
|
(= Package-Name). Wenn MainActivity affinity="" hätte,
|
||||||
|
würde der Custom-Tab-Redirect mit FLAG_ACTIVITY_NEW_TASK
|
||||||
|
in einem zweiten Task + Prozess landen, AppAuth verliert
|
||||||
|
seinen in-memory PKCE-State und meldet
|
||||||
|
"No stored state - unable to handle response".
|
||||||
|
-->
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
@ -40,15 +49,13 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!--
|
||||||
<intent-filter android:autoVerify="true">
|
Der OIDC-Redirect (holzleitner://oauth2redirect) wird NICHT
|
||||||
<action android:name="android.intent.action.VIEW" />
|
hier abgefangen, sondern in der von flutter_appauth zur
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
Laufzeit eingehängten RedirectUriReceiverActivity (siehe
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
merged AndroidManifest und manifestPlaceholders in
|
||||||
<data
|
build.gradle.kts).
|
||||||
android:scheme="myapp"
|
-->
|
||||||
android:host="callback" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
Erlaubt HTTP-Klartext-Zugriff auf die lokalen Dev-Hosts:
|
DEV-Konfiguration: erlaubt HTTP-Klartext-Traffic zu allen Hosts.
|
||||||
- 10.0.2.2: Android-Emulator-Loopback zum Mac-Host
|
Begründung: das Rust-Backend + Keycloak laufen während der
|
||||||
- localhost / 127.0.0.1: Geräte-internes Loopback
|
Entwicklung als Docker-Container auf dem Mac und werden je nach
|
||||||
- 192.168.x.x: spätere Tests gegen LAN-IP des Entwickler-Macs
|
Test-Setup unter unterschiedlichen Adressen erreicht
|
||||||
|
(localhost / 10.0.2.2 / LAN-IP). Android-NetworkSecurityConfig kann
|
||||||
|
keine IP-Range-Wildcards (192.168.*.*), deshalb hier base-config
|
||||||
|
statt einzelner domain-config-Einträge.
|
||||||
|
|
||||||
In Produktion entfernen, sobald Backend nur noch über HTTPS
|
*** Vor jedem Produktiv-/Beta-Release entfernen ***
|
||||||
erreichbar ist.
|
Für Produktion: cleartextTrafficPermitted="false" als Default lassen
|
||||||
|
und nur die echten HTTPS-Backend-Hosts whitelisten.
|
||||||
-->
|
-->
|
||||||
<network-security-config>
|
<network-security-config>
|
||||||
<domain-config cleartextTrafficPermitted="true">
|
<base-config cleartextTrafficPermitted="true">
|
||||||
<domain includeSubdomains="false">10.0.2.2</domain>
|
<trust-anchors>
|
||||||
<domain includeSubdomains="false">localhost</domain>
|
<certificates src="system" />
|
||||||
<domain includeSubdomains="false">127.0.0.1</domain>
|
</trust-anchors>
|
||||||
</domain-config>
|
</base-config>
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
|
|||||||
@ -84,6 +84,14 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
|
|||||||
// würde sonst abbrechen. In Produktion (HTTPS) ist das ein
|
// würde sonst abbrechen. In Produktion (HTTPS) ist das ein
|
||||||
// No-Op und kann bleiben.
|
// No-Op und kann bleiben.
|
||||||
allowInsecureConnections: true,
|
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'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user