# 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<String, String> update(
            MultivaluedMap<String, String> incomingHeaders,
            MultivaluedMap<String, String> clientOutgoingHeaders) {

        MultivaluedMap<String, String> 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
