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: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 -->
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'],
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user