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:
240
configure-keycloak-mobile.sh
Normal file
240
configure-keycloak-mobile.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user