feat(mobile): Implement Keycloak WebView authentication with HTTP callback

- Replace flutter_appauth with custom WebView implementation to resolve deep link issues
- Add KeycloakWebViewAuthService with integrated WebView for seamless authentication
- Configure Android manifest for HTTP cleartext traffic support
- Add network security config for development environment (192.168.1.11)
- Update Keycloak client to use HTTP callback endpoint (http://192.168.1.11:8080/auth/callback)
- Remove obsolete keycloak_auth_service.dart and temporary scripts
- Clean up dependencies and regenerate injection configuration
- Tested successfully on multiple Android devices (Xiaomi 2201116TG, SM A725F)

BREAKING CHANGE: Authentication flow now uses WebView instead of external browser
- Users will see Keycloak login page within the app instead of browser redirect
- Resolves ERR_CLEARTEXT_NOT_PERMITTED and deep link state management issues
- Maintains full OIDC compliance with PKCE flow and secure token storage

Technical improvements:
- WebView with custom navigation delegate for callback handling
- Automatic token extraction and user info parsing from JWT
- Proper error handling and user feedback
- Consistent authentication state management across app lifecycle
This commit is contained in:
DahoudG
2025-09-15 01:44:16 +00:00
parent 73459b3092
commit f89f6167cc
290 changed files with 34563 additions and 3528 deletions

View File

@@ -1,13 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Permissions pour les communications -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="unionflow_mobile_apps"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@@ -20,10 +28,19 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Intent filter standard -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Intent filter pour flutter_appauth -->
<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="${appAuthRedirectScheme}" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@@ -41,5 +58,17 @@
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
<!-- Queries pour les communications -->
<intent>
<action android:name="android.intent.action.CALL"/>
</intent>
<intent>
<action android:name="android.intent.action.SENDTO"/>
<data android:scheme="sms"/>
</intent>
<intent>
<action android:name="android.intent.action.SENDTO"/>
<data android:scheme="mailto"/>
</intent>
</queries>
</manifest>

View File

@@ -1,5 +1,29 @@
package dev.lions.unionflow_mobile_apps
import android.content.Intent
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent?.action == Intent.ACTION_VIEW) {
val data = intent.data
if (data != null && data.scheme == "dev.lions.unionflow-mobile") {
// L'intent sera automatiquement traité par flutter_appauth
android.util.Log.d("MainActivity", "Deep link reçu: $data")
}
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.11</domain>
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config>