# Solution: Propagation du Token JWT depuis JSF vers Backend **Date**: 2025-12-05 **Problème**: 401 Unauthorized lors des appels frontend → backend malgré authentification OIDC réussie --- ## 🔍 Analyse du Problème ### Symptômes Observés 1. **Frontend** (Port 8080): - ✅ Authentification OIDC réussie avec PKCE - ✅ Token JWT reçu avec tous les rôles dans `realm_access.roles` - ❌ Erreur: `Received: 'Unauthorized, status code 401'` lors des appels API 2. **Backend** (Port 8081): - ✅ Démarre sans erreur - ❌ Logs: `Bearer access token is not available` - ❌ Rejette les requêtes avec 401 Unauthorized ### Configuration Initiale (Insuffisante) ```properties # application.properties:56 quarkus.rest-client."lions-user-manager-api".bearer-token-propagation=true ``` ### Pourquoi ça ne fonctionnait pas ? La propriété `bearer-token-propagation=true` ne fonctionne QUE pour: - ✅ Appels **backend → backend** (service-to-service) - ❌ Appels **JSF managed bean → backend** (notre cas) **Raison technique**: Les managed beans JSF s'exécutent dans un contexte serveur différent où le token OIDC n'est pas automatiquement injecté dans les appels REST Client. --- ## ✅ Solution Implémentée ### 1. Création de `AuthHeaderFactory` Factory personnalisé qui intercepte TOUS les appels REST Client et ajoute automatiquement le header Authorization avec le token JWT. **Fichier**: `lions-user-manager-client-quarkus-primefaces-freya/src/main/java/dev/lions/user/manager/client/filter/AuthHeaderFactory.java` ```java @ApplicationScoped public class AuthHeaderFactory implements ClientHeadersFactory { private static final Logger LOGGER = Logger.getLogger(AuthHeaderFactory.class.getName()); @Inject JsonWebToken jwt; @Override public MultivaluedMap update( MultivaluedMap incomingHeaders, MultivaluedMap clientOutgoingHeaders) { MultivaluedMap result = new MultivaluedHashMap<>(); try { // Vérifier si le JWT est disponible et non expiré if (jwt != null && jwt.getRawToken() != null && !jwt.getRawToken().isEmpty()) { String token = jwt.getRawToken(); result.add("Authorization", "Bearer " + token); LOGGER.fine("Token Bearer ajouté au header Authorization"); } else { LOGGER.warning("Token JWT non disponible ou vide"); } } catch (Exception e) { LOGGER.severe("Erreur lors de l'ajout du token Bearer: " + e.getMessage()); } return result; } } ``` **Points clés**: - `@ApplicationScoped` - Bean CDI singleton - `@Inject JsonWebToken jwt` - Injecte le token OIDC actuel - `jwt.getRawToken()` - Récupère le token brut (chaîne Base64) - Ajoute `Authorization: Bearer {token}` à chaque requête ### 2. Enregistrement sur tous les REST Clients Ajout de l'annotation `@RegisterClientHeaders(AuthHeaderFactory.class)` sur chaque interface REST Client. #### UserServiceClient ```java @Path("/api/users") @RegisterRestClient(configKey = "lions-user-manager-api") @RegisterClientHeaders(AuthHeaderFactory.class) // ← AJOUTÉ @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public interface UserServiceClient { // ... } ``` #### Autres REST Clients modifiés - `RoleServiceClient.java:19` - `AuditServiceClient.java:20` - `SyncServiceClient.java:16` --- ## 🧪 Test de la Solution ### 1. Recompiler le Frontend ```bash cd lions-user-manager-client-quarkus-primefaces-freya mvn compile ``` **Résultat attendu**: BUILD SUCCESS ✅ ### 2. Redémarrer le Frontend (si nécessaire) Si le frontend ne recharge pas automatiquement les changements: ```bash # Arrêter le frontend actuel (Ctrl+C) mvn quarkus:dev ``` ### 3. Test Complet 1. Accéder à http://localhost:8080 2. **Se déconnecter** (important pour obtenir un nouveau token) 3. **Se reconnecter** avec `testuser` / `test123` 4. Naviguer vers http://localhost:8080/pages/user-manager/users/list.xhtml 5. **Vérifier**: La liste des utilisateurs se charge sans erreur 401 ### 4. Vérification des Logs #### Frontend - Token propagé ``` FINE Token Bearer ajouté au header Authorization ``` #### Backend - Token reçu et validé ``` DEBUG [io.qu.oi.ru.BearerAuthenticationMechanism] Token validation succeeded ``` Si vous voyez encore `Bearer access token is not available` → le token n'est toujours pas propagé (problème de contexte CDI ou hot reload). --- ## 📊 Comparaison Avant/Après ### AVANT (avec bearer-token-propagation uniquement) ``` Frontend JSF Bean → REST Client → Backend ↓ ❌ Pas de token ↓ 401 Unauthorized ``` ### APRÈS (avec AuthHeaderFactory) ``` Frontend JSF Bean → REST Client → AuthHeaderFactory ↓ @Inject JsonWebToken ↓ Authorization: Bearer {token} ↓ Backend ↓ ✅ Token validé ↓ 200 OK + Données ``` --- ## 🔧 Architecture Technique ### Flux d'exécution complet 1. **Utilisateur s'authentifie** via OIDC (Keycloak) - PKCE flow avec S256 - Redirection vers Keycloak → Retour avec code → Échange contre tokens 2. **Quarkus OIDC reçoit les tokens** - `access_token` (contient `realm_access.roles`) - `id_token` (identité utilisateur) - `refresh_token` (renouvellement) 3. **Token injecté dans contexte CDI** - `JsonWebToken` bean disponible via `@Inject` - Contient toutes les claims du token 4. **JSF Bean appelle REST Client** - Ex: `userServiceClient.searchUsers(criteria)` 5. **AuthHeaderFactory intercepte l'appel** - Méthode `update()` appelée avant l'envoi HTTP - Injecte `Authorization: Bearer {access_token}` 6. **Backend reçoit la requête** - `BearerAuthenticationMechanism` extrait le token - Valide la signature JWT avec clé publique Keycloak - Extrait les rôles depuis `realm_access.roles` - Autorise l'accès si rôles suffisants 7. **Backend retourne les données** - HTTP 200 OK + JSON response --- ## 🎯 Points Importants ### Pourquoi JsonWebToken et pas d'autres solutions ? 1. ✅ **Native Quarkus** - Fait partie du stack OIDC standard 2. ✅ **Thread-safe** - Géré par CDI avec contexte de requête 3. ✅ **Type-safe** - Interface fortement typée 4. ✅ **Validation automatique** - Token déjà validé par Quarkus OIDC ### Alternatives (non retenues) - ❌ `SecurityContext` - Ne contient pas le token brut - ❌ `OidcSession` - Trop couplé à la session HTTP - ❌ Header manuel dans chaque méthode - Code dupliqué et fragile - ❌ Filter JAX-RS - Plus complexe, moins naturel avec REST Client ### Avantages de cette solution 1. **Automatique** - Aucun code dans les beans JSF 2. **Centralisé** - Une seule classe factory 3. **Réutilisable** - Fonctionne pour tous les REST Clients 4. **Maintenable** - Facile à déboguer et à tester 5. **Performant** - Aucune copie du token, juste une référence --- ## 📝 Checklist de Validation Après implémentation de cette solution: ### Frontend - [x] `AuthHeaderFactory.java` créé dans `client/filter/` - [x] Tous les REST Clients annotés avec `@RegisterClientHeaders` - [x] Compilation Maven réussie - [x] Aucune erreur de hot reload ### Runtime - [ ] Se déconnecter puis reconnecter pour obtenir nouveau token - [ ] Naviguer vers la liste des utilisateurs - [ ] Vérifier logs frontend: "Token Bearer ajouté au header Authorization" - [ ] Vérifier logs backend: "Token validation succeeded" - [ ] Liste des utilisateurs s'affiche sans erreur 401 ### Backend - [ ] Backend accepte les requêtes avec token - [ ] Rôles correctement extraits et appliqués - [ ] Pas de logs "Bearer access token is not available" --- ## 🐛 Troubleshooting ### Problème: Token toujours pas propagé après changements **Cause**: Hot reload Quarkus n'a pas détecté les changements de factory **Solution**: ```bash # Arrêter le frontend (Ctrl+C) cd lions-user-manager-client-quarkus-primefaces-freya mvn clean compile quarkus:dev ``` ### Problème: "Token JWT non disponible ou vide" **Cause**: Contexte CDI ne trouve pas le JsonWebToken **Solution**: 1. Vérifier que l'utilisateur est authentifié 2. Se déconnecter et reconnecter 3. Vérifier logs OIDC: token doit être présent ### Problème: 401 sur certaines pages mais pas d'autres **Cause**: Chemins publics mal configurés **Solution**: Vérifier `application.properties`: ```properties quarkus.http.auth.permission.public.paths=/,/index.xhtml,... quarkus.http.auth.permission.authenticated.paths=/pages/user-manager/* ``` --- ## 📚 Références ### Quarkus Documentation - [Quarkus OIDC Token Propagation](https://quarkus.io/guides/security-openid-connect-client-reference#token-propagation) - [Quarkus REST Client](https://quarkus.io/guides/rest-client) - [ClientHeadersFactory](https://download.eclipse.org/microprofile/microprofile-rest-client-2.0/microprofile-rest-client-spec-2.0.html#_clientheadersfactory) ### Keycloak - [JWT Token Structure](https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange) - [Realm Roles vs Client Roles](https://www.keycloak.org/docs/latest/server_admin/#realm-roles) --- **Auteur**: Claude Code **Date**: 2025-12-05 **Version**: 1.0.0