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

284
setup-keycloak.sh Normal file
View File

@@ -0,0 +1,284 @@
#!/bin/bash
# Configuration Keycloak pour UnionFlow
# Auteur: UnionFlow Team
echo "🔐 Configuration automatique de Keycloak pour UnionFlow"
echo "======================================================="
# Variables de configuration
KEYCLOAK_URL="http://localhost:8180"
ADMIN_USER="admin"
ADMIN_PASSWORD="admin"
REALM_NAME="unionflow"
CLIENT_ID="unionflow-server"
CLIENT_SECRET="unionflow-secret-2025"
# Couleurs pour l'affichage
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Fonction pour obtenir le token d'accès admin
get_admin_token() {
echo -e "${YELLOW}📡 Obtention du token d'administration...${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_USER&password=$ADMIN_PASSWORD&grant_type=password&client_id=admin-cli")
if [ $? -eq 0 ]; then
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$ACCESS_TOKEN" ]; then
echo -e "${GREEN}✅ Token obtenu avec succès${NC}"
return 0
fi
fi
echo -e "${RED}❌ Erreur lors de l'obtention du token${NC}"
echo "Réponse: $TOKEN_RESPONSE"
exit 1
}
# Fonction pour créer le realm UnionFlow
create_realm() {
echo -e "${YELLOW}🏛️ Création du realm '$REALM_NAME'...${NC}"
# Vérifier si le realm existe déjà
REALM_CHECK=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-w "%{http_code}")
if [[ "$REALM_CHECK" == *"200"* ]]; then
echo -e "${YELLOW}⚠️ Le realm '$REALM_NAME' existe déjà. Suppression...${NC}"
curl -s -X DELETE "$KEYCLOAK_URL/admin/realms/$REALM_NAME" \
-H "Authorization: Bearer $ACCESS_TOKEN"
sleep 2
fi
# Créer le nouveau realm
REALM_CONFIG='{
"realm": "'$REALM_NAME'",
"displayName": "UnionFlow",
"enabled": true,
"registrationAllowed": true,
"registrationEmailAsUsername": true,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"sslRequired": "external",
"defaultLocale": "fr",
"internationalizationEnabled": true,
"supportedLocales": ["fr", "en"]
}'
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éé avec succès${NC}"
sleep 2
else
echo -e "${RED}❌ Erreur lors de la création du realm${NC}"
echo "Réponse: $RESPONSE"
exit 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",
"description": "Client pour l API serveur UnionFlow",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "'$CLIENT_SECRET'",
"protocol": "openid-connect",
"publicClient": false,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"redirectUris": ["http://localhost:8080/*", "http://localhost:3000/*"],
"webOrigins": ["http://localhost:8080", "http://localhost:3000", "*"],
"fullScopeAllowed": true,
"attributes": {
"access.token.lifespan": "3600"
}
}'
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éé avec succès${NC}"
echo -e "${CYAN}🔑 Secret du client: $CLIENT_SECRET${NC}"
else
echo -e "${RED}❌ Erreur lors de la création du client${NC}"
echo "Réponse: $RESPONSE"
exit 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")
DESCRIPTIONS=("Administrateur système avec tous les droits"
"Président de l'union avec droits de gestion complète"
"Secrétaire avec droits de gestion des membres et événements"
"Trésorier avec droits de gestion financière"
"Gestionnaire des membres avec droits de CRUD sur les membres"
"Organisateur d'événements avec droits de gestion des événements"
"Membre standard avec droits de consultation")
for i in "${!ROLES[@]}"; do
ROLE_NAME="${ROLES[$i]}"
ROLE_DESC="${DESCRIPTIONS[$i]}"
ROLE_CONFIG='{
"name": "'$ROLE_NAME'",
"description": "'$ROLE_DESC'"
}'
RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$ROLE_CONFIG" \
-w "%{http_code}")
if [[ "$RESPONSE" == *"201"* ]]; then
echo -e " ${GREEN}✅ Rôle '$ROLE_NAME' créé${NC}"
else
echo -e " ${YELLOW}⚠️ Rôle '$ROLE_NAME' existe déjà ou erreur${NC}"
fi
done
}
# Fonction pour créer un utilisateur
create_user() {
local username=$1
local email=$2
local firstname=$3
local lastname=$4
local password=$5
shift 5
local roles=("$@")
USER_CONFIG='{
"username": "'$username'",
"email": "'$email'",
"firstName": "'$firstname'",
"lastName": "'$lastname'",
"enabled": true,
"emailVerified": true,
"credentials": [{
"type": "password",
"value": "'$password'",
"temporary": false
}]
}'
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 '$username' créé${NC}"
# Récupérer l'ID de l'utilisateur
USER_ID=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/users?username=$username" \
-H "Authorization: Bearer $ACCESS_TOKEN" | \
grep -o '"id":"[^"]*' | head -1 | cut -d'"' -f4)
if [ -n "$USER_ID" ]; then
# Assigner les rôles
for role in "${roles[@]}"; do
# Récupérer les détails du rôle
ROLE_DATA=$(curl -s -X GET "$KEYCLOAK_URL/admin/realms/$REALM_NAME/roles/$role" \
-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" > /dev/null
echo -e " ${GREEN}✅ Rôle '$role' assigné à '$username'${NC}"
fi
done
fi
else
echo -e " ${YELLOW}⚠️ Utilisateur '$username' existe déjà ou erreur${NC}"
fi
}
# Fonction pour créer les utilisateurs de test
create_test_users() {
echo -e "${YELLOW}👤 Création des utilisateurs de test...${NC}"
create_user "admin" "admin@unionflow.dev" "Administrateur" "Système" "admin123" "ADMIN" "PRESIDENT"
create_user "president" "president@unionflow.dev" "Jean" "Dupont" "president123" "PRESIDENT" "MEMBRE"
create_user "secretaire" "secretaire@unionflow.dev" "Marie" "Martin" "secretaire123" "SECRETAIRE" "GESTIONNAIRE_MEMBRE" "MEMBRE"
create_user "tresorier" "tresorier@unionflow.dev" "Pierre" "Durand" "tresorier123" "TRESORIER" "MEMBRE"
create_user "membre1" "membre1@unionflow.dev" "Sophie" "Bernard" "membre123" "MEMBRE"
}
# Script principal
main() {
# Obtenir le token d'administration
get_admin_token
# Créer le realm
create_realm
# Créer le client
create_client
# Créer les rôles
create_roles
# Créer les utilisateurs de test
create_test_users
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}👤 Utilisateurs de test créés :${NC}"
echo -e " • admin / admin123 (ADMIN, PRESIDENT)"
echo -e " • president / president123 (PRESIDENT, MEMBRE)"
echo -e " • secretaire / secretaire123 (SECRETAIRE, GESTIONNAIRE_MEMBRE, MEMBRE)"
echo -e " • tresorier / tresorier123 (TRESORIER, MEMBRE)"
echo -e " • membre1 / membre123 (MEMBRE)"
echo ""
echo -e "${YELLOW}🔧 Prochaine étape: Mettre à jour application.properties${NC}"
}
# Exécuter le script principal
main