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

@@ -0,0 +1,240 @@
#!/bin/bash
# Configuration automatique du client mobile Keycloak pour UnionFlow
# Ce script configure le client unionflow-mobile dans Keycloak
set -e
# Configuration
KEYCLOAK_URL="http://localhost:8180"
REALM="unionflow"
ADMIN_USER="admin"
ADMIN_PASSWORD="admin"
CLIENT_ID="unionflow-mobile"
echo "🔧 Configuration automatique du client mobile Keycloak..."
echo "📍 Keycloak URL: $KEYCLOAK_URL"
echo "🏛️ Realm: $REALM"
echo "📱 Client ID: $CLIENT_ID"
echo ""
# Fonction pour obtenir le token d'administration
get_admin_token() {
echo "🔑 Obtention du token d'administration..."
ADMIN_TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=$ADMIN_USER" \
-d "password=$ADMIN_PASSWORD" \
-d "grant_type=password" \
-d "client_id=admin-cli" | jq -r '.access_token')
if [ "$ADMIN_TOKEN" = "null" ] || [ -z "$ADMIN_TOKEN" ]; then
echo "❌ Erreur: Impossible d'obtenir le token d'administration"
echo "Vérifiez les credentials Keycloak (admin/admin)"
exit 1
fi
echo "✅ Token d'administration obtenu"
}
# Fonction pour vérifier si le client existe déjà
check_client_exists() {
echo "🔍 Vérification de l'existence du client $CLIENT_ID..."
CLIENT_EXISTS=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" | jq -r ".[] | select(.clientId==\"$CLIENT_ID\") | .id")
if [ -n "$CLIENT_EXISTS" ] && [ "$CLIENT_EXISTS" != "null" ]; then
echo "⚠️ Client $CLIENT_ID existe déjà (ID: $CLIENT_EXISTS)"
return 0
else
echo " Client $CLIENT_ID n'existe pas, création nécessaire"
return 1
fi
}
# Fonction pour créer le client mobile
create_mobile_client() {
echo "📱 Création du client mobile $CLIENT_ID..."
CLIENT_CONFIG='{
"clientId": "'$CLIENT_ID'",
"name": "UnionFlow Mobile App",
"description": "Application mobile UnionFlow avec authentification OIDC",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"publicClient": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"authorizationServicesEnabled": false,
"rootUrl": "com.unionflow.mobile://",
"baseUrl": "com.unionflow.mobile://home",
"redirectUris": [
"com.unionflow.mobile://login-callback",
"com.unionflow.mobile://login-callback/*"
],
"postLogoutRedirectUris": [
"com.unionflow.mobile://logout-callback",
"com.unionflow.mobile://logout-callback/*"
],
"webOrigins": ["+"],
"attributes": {
"pkce.code.challenge.method": "S256",
"access.token.lifespan": "900",
"client.session.idle.timeout": "1800",
"client.session.max.lifespan": "43200"
},
"defaultClientScopes": ["openid", "profile", "email", "roles"],
"optionalClientScopes": []
}'
RESPONSE=$(curl -s -w "%{http_code}" -X POST "$KEYCLOAK_URL/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$CLIENT_CONFIG")
HTTP_CODE="${RESPONSE: -3}"
if [ "$HTTP_CODE" = "201" ]; then
echo "✅ Client mobile créé avec succès"
# Récupérer l'ID du client créé
CLIENT_UUID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" | jq -r ".[] | select(.clientId==\"$CLIENT_ID\") | .id")
echo "📋 Client UUID: $CLIENT_UUID"
return 0
else
echo "❌ Erreur lors de la création du client (HTTP: $HTTP_CODE)"
echo "Response: ${RESPONSE%???}"
return 1
fi
}
# Fonction pour configurer les mappers de rôles
configure_role_mappers() {
echo "🎭 Configuration des mappers de rôles..."
# Mapper pour l'audience
AUDIENCE_MAPPER='{
"name": "audience-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"config": {
"included.client.audience": "unionflow-server",
"access.token.claim": "true"
}
}'
# Mapper pour les rôles client
ROLES_MAPPER='{
"name": "client-roles-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-client-role-mapper",
"config": {
"client.id": "unionflow-server",
"claim.name": "resource_access.unionflow-server.roles",
"access.token.claim": "true",
"id.token.claim": "false",
"userinfo.token.claim": "false",
"multivalued": "true"
}
}'
# Ajouter le mapper d'audience
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM/clients/$CLIENT_UUID/protocol-mappers/models" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$AUDIENCE_MAPPER" > /dev/null
# Ajouter le mapper de rôles
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM/clients/$CLIENT_UUID/protocol-mappers/models" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$ROLES_MAPPER" > /dev/null
echo "✅ Mappers de rôles configurés"
}
# Fonction pour tester la configuration
test_configuration() {
echo "🧪 Test de la configuration..."
# Test de l'endpoint d'autorisation
AUTH_URL="$KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/auth"
echo "📍 URL d'autorisation: $AUTH_URL"
# Test de l'endpoint de token
TOKEN_URL="$KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/token"
echo "📍 URL de token: $TOKEN_URL"
# Vérifier que les endpoints répondent
AUTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$AUTH_URL?client_id=$CLIENT_ID&response_type=code&redirect_uri=com.unionflow.mobile://login-callback")
if [ "$AUTH_STATUS" = "200" ] || [ "$AUTH_STATUS" = "302" ]; then
echo "✅ Endpoint d'autorisation accessible"
else
echo "⚠️ Endpoint d'autorisation: HTTP $AUTH_STATUS"
fi
echo "✅ Configuration testée"
}
# Fonction principale
main() {
echo "🚀 Début de la configuration automatique..."
echo ""
# Vérifier que jq est installé
if ! command -v jq &> /dev/null; then
echo "❌ Erreur: jq n'est pas installé"
echo "Installez jq avec: sudo apt-get install jq (Ubuntu) ou brew install jq (macOS)"
exit 1
fi
# Obtenir le token d'administration
get_admin_token
# Vérifier si le client existe déjà
if check_client_exists; then
echo " Client existant trouvé, récupération de l'UUID..."
CLIENT_UUID="$CLIENT_EXISTS"
else
# Créer le client mobile
if create_mobile_client; then
CLIENT_UUID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM/clients" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" | jq -r ".[] | select(.clientId==\"$CLIENT_ID\") | .id")
else
echo "❌ Échec de la création du client"
exit 1
fi
fi
# Configurer les mappers de rôles
configure_role_mappers
# Tester la configuration
test_configuration
echo ""
echo "🎉 Configuration terminée avec succès !"
echo ""
echo "📋 Résumé de la configuration:"
echo " • Client ID: $CLIENT_ID"
echo " • Client UUID: $CLIENT_UUID"
echo " • Type: Public (PKCE activé)"
echo " • Redirect URI: com.unionflow.mobile://login-callback"
echo " • Logout URI: com.unionflow.mobile://logout-callback"
echo ""
echo "🔗 URLs importantes:"
echo " • Authorization: $KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/auth"
echo " • Token: $KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/token"
echo " • Logout: $KEYCLOAK_URL/realms/$REALM/protocol/openid-connect/logout"
echo ""
echo "✅ L'application mobile peut maintenant s'authentifier avec Keycloak !"
}
# Exécuter le script principal
main "$@"