- 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
241 lines
8.4 KiB
Bash
241 lines
8.4 KiB
Bash
#!/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 "$@"
|