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

298
complete-keycloak-setup.sh Normal file
View File

@@ -0,0 +1,298 @@
#!/bin/bash
# Configuration complète de Keycloak pour UnionFlow
echo "🔐 Configuration complète de Keycloak pour UnionFlow"
echo "===================================================="
# Variables
KEYCLOAK_URL="http://localhost:8180"
REALM_NAME="unionflow"
CLIENT_ID="unionflow-server"
CLIENT_SECRET="unionflow-secret-2025"
# Couleurs
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# Fonction pour obtenir un token admin
get_admin_token() {
echo -e "${YELLOW}📡 Obtention du token admin...${NC}"
TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin&grant_type=password&client_id=admin-cli")
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$ACCESS_TOKEN" ]; then
echo -e "${GREEN}✅ Token admin obtenu${NC}"
return 0
else
echo -e "${RED}❌ Impossible d'obtenir le token admin${NC}"
echo "Réponse: $TOKEN_RESPONSE"
return 1
fi
}
# Fonction pour supprimer et recréer le realm
recreate_realm() {
echo -e "${YELLOW}🏛️ Suppression et recréation du realm '$REALM_NAME'...${NC}"
# Supprimer le realm s'il existe
curl -s -X DELETE "$KEYCLOAK_URL/admin/realms/$REALM_NAME" \
-H "Authorization: Bearer $ACCESS_TOKEN" > /dev/null
sleep 2
# Créer le nouveau realm
REALM_CONFIG='{
"realm": "'$REALM_NAME'",
"displayName": "UnionFlow",
"enabled": true,
"registrationAllowed": false,
"registrationEmailAsUsername": true,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"sslRequired": "external",
"defaultLocale": "fr"
}'
RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/admin/realms" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$REALM_CONFIG" \
-w "%{http_code}")
if [[ "$RESPONSE" == *"201"* ]]; then
echo -e "${GREEN}✅ Realm '$REALM_NAME' créé${NC}"
sleep 2
return 0
else
echo -e "${RED}❌ Erreur lors de la création du realm${NC}"
return 1
fi
}
# Fonction pour créer le client
create_client() {
echo -e "${YELLOW}🔧 Création du client '$CLIENT_ID'...${NC}"
CLIENT_CONFIG='{
"clientId": "'$CLIENT_ID'",
"name": "UnionFlow Server API",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "'$CLIENT_SECRET'",
"protocol": "openid-connect",
"publicClient": false,
"serviceAccountsEnabled": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"authorizationServicesEnabled": false,
"redirectUris": ["http://localhost:8080/*"],
"webOrigins": ["http://localhost:8080", "*"],
"fullScopeAllowed": true
}'
RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/clients" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$CLIENT_CONFIG" \
-w "%{http_code}")
if [[ "$RESPONSE" == *"201"* ]]; then
echo -e "${GREEN}✅ Client '$CLIENT_ID' créé${NC}"
return 0
else
echo -e "${RED}❌ Erreur lors de la création du client${NC}"
return 1
fi
}
# Fonction pour créer les rôles
create_roles() {
echo -e "${YELLOW}👥 Création des rôles...${NC}"
ROLES=("ADMIN" "PRESIDENT" "SECRETAIRE" "TRESORIER" "GESTIONNAIRE_MEMBRE" "ORGANISATEUR_EVENEMENT" "MEMBRE")
for ROLE_NAME in "${ROLES[@]}"; do
ROLE_CONFIG='{
"name": "'$ROLE_NAME'",
"description": "Rôle '$ROLE_NAME' pour UnionFlow"
}'
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$ROLE_CONFIG" > /dev/null
echo -e " ${GREEN}✅ Rôle '$ROLE_NAME' créé${NC}"
done
}
# Fonction pour créer un utilisateur de test
create_test_user() {
echo -e "${YELLOW}👤 Création de l'utilisateur de test...${NC}"
USER_CONFIG='{
"username": "testuser",
"email": "test@unionflow.dev",
"firstName": "Test",
"lastName": "User",
"enabled": true,
"emailVerified": true
}'
RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$USER_CONFIG" \
-w "%{http_code}")
if [[ "$RESPONSE" == *"201"* ]]; then
echo -e "${GREEN}✅ Utilisateur créé${NC}"
# Récupérer l'ID de l'utilisateur
USER_ID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=testuser" \
-H "Authorization: Bearer $ACCESS_TOKEN" | \
grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4)
if [ -n "$USER_ID" ]; then
# Définir le mot de passe
PASSWORD_CONFIG='{
"type": "password",
"value": "test123",
"temporary": false
}'
curl -s -X PUT "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/reset-password" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$PASSWORD_CONFIG"
echo -e "${GREEN}✅ Mot de passe défini${NC}"
# Assigner le rôle MEMBRE
ROLE_DATA=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles/MEMBRE" \
-H "Authorization: Bearer $ACCESS_TOKEN")
if [[ "$ROLE_DATA" == *'"name"'* ]]; then
ROLE_ASSIGNMENT="[$ROLE_DATA]"
curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users/$USER_ID/role-mappings/realm" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$ROLE_ASSIGNMENT"
echo -e "${GREEN}✅ Rôle MEMBRE assigné${NC}"
fi
fi
return 0
else
echo -e "${RED}❌ Erreur lors de la création de l'utilisateur${NC}"
return 1
fi
}
# Fonction pour tester l'authentification
test_authentication() {
echo -e "${YELLOW}🧪 Test d'authentification...${NC}"
AUTH_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/$REALM_NAME/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=testuser&password=test123&grant_type=password&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET")
AUTH_TOKEN=$(echo $AUTH_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$AUTH_TOKEN" ]; then
echo -e "${GREEN}✅ Authentification réussie !${NC}"
echo -e "${CYAN}🔑 Token obtenu (tronqué): ${AUTH_TOKEN:0:50}...${NC}"
# Test d'accès à l'API
echo -e "${YELLOW}🧪 Test d'accès à l'API UnionFlow...${NC}"
API_RESPONSE=$(curl -s -w "%{http_code}" -H "Authorization: Bearer $AUTH_TOKEN" "http://localhost:8080/api/organisations")
HTTP_CODE=$(echo "$API_RESPONSE" | tail -c 4)
if [ "$HTTP_CODE" = "200" ]; then
echo -e "${GREEN}✅ Accès API réussi !${NC}"
elif [ "$HTTP_CODE" = "403" ]; then
echo -e "${YELLOW}⚠️ Accès refusé - Permissions insuffisantes (normal pour un utilisateur MEMBRE)${NC}"
else
echo -e "${YELLOW}⚠️ Code de réponse: $HTTP_CODE${NC}"
fi
return 0
else
echo -e "${RED}❌ Échec de l'authentification${NC}"
echo "Réponse: $AUTH_RESPONSE"
return 1
fi
}
# Script principal
main() {
echo -e "${CYAN}🚀 Démarrage de la configuration complète...${NC}"
echo ""
# Obtenir le token admin
if ! get_admin_token; then
exit 1
fi
# Recréer le realm
if ! recreate_realm; then
exit 1
fi
# Créer le client
if ! create_client; then
exit 1
fi
# Créer les rôles
create_roles
# Créer l'utilisateur de test
if ! create_test_user; then
exit 1
fi
# Tester l'authentification
if test_authentication; then
echo ""
echo -e "${GREEN}🎉 CONFIGURATION KEYCLOAK TERMINÉE AVEC SUCCÈS !${NC}"
echo -e "${GREEN}===============================================${NC}"
echo -e "${CYAN}📋 Informations de configuration :${NC}"
echo -e " • Realm: $REALM_NAME"
echo -e " • Client ID: $CLIENT_ID"
echo -e " • Client Secret: $CLIENT_SECRET"
echo -e " • URL Auth Server: $KEYCLOAK_URL/realms/$REALM_NAME"
echo ""
echo -e "${CYAN}👤 Utilisateur de test :${NC}"
echo -e " • Username: testuser"
echo -e " • Password: test123"
echo -e " • Rôle: MEMBRE"
echo ""
echo -e "${CYAN}🔗 URLs importantes :${NC}"
echo -e " • UnionFlow API: http://localhost:8080"
echo -e " • Swagger UI: http://localhost:8080/q/swagger-ui"
echo -e " • Health Check: http://localhost:8080/health"
echo -e " • Keycloak Admin: http://localhost:8180/admin"
echo ""
echo -e "${GREEN}✅ L'intégration Keycloak avec UnionFlow est maintenant fonctionnelle !${NC}"
else
echo -e "${RED}❌ Échec de la configuration${NC}"
exit 1
fi
}
# Exécuter le script principal
main