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:
Dennis Nemec
2026-05-15 11:16:18 +02:00
parent 08824290ff
commit cb22fff407
3 changed files with 40 additions and 21 deletions

View File

@ -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">
<!--
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
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
@ -40,15 +49,13 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="callback" />
</intent-filter>
<!--
Der OIDC-Redirect (holzleitner://oauth2redirect) wird NICHT
hier abgefangen, sondern in der von flutter_appauth zur
Laufzeit eingehängten RedirectUriReceiverActivity (siehe
merged AndroidManifest und manifestPlaceholders in
build.gradle.kts).
-->
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

View File

@ -1,17 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Erlaubt HTTP-Klartext-Zugriff auf die lokalen Dev-Hosts:
- 10.0.2.2: Android-Emulator-Loopback zum Mac-Host
- localhost / 127.0.0.1: Geräte-internes Loopback
- 192.168.x.x: spätere Tests gegen LAN-IP des Entwickler-Macs
DEV-Konfiguration: erlaubt HTTP-Klartext-Traffic zu allen Hosts.
Begründung: das Rust-Backend + Keycloak laufen während der
Entwicklung als Docker-Container auf dem Mac und werden je nach
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
erreichbar ist.
*** Vor jedem Produktiv-/Beta-Release entfernen ***
Für Produktion: cleartextTrafficPermitted="false" als Default lassen
und nur die echten HTTPS-Backend-Hosts whitelisten.
-->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">10.0.2.2</domain>
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>