Authentification JWT
Bemba authentifie les appels à l'API par des jetons JWT (LexikJWTAuthenticationBundle) et renouvelle la session par des jetons de rafraîchissement stockés en base (Gesdinet).
Jetons
| Jeton | Durée | Où il circule |
|---|---|---|
| Jeton d'accès (JWT signé RSA) | 1 heure (token_ttl: 3600) | cookie BEARER (HttpOnly) ; en-tête Authorization: Bearer … accepté aussi |
| Jeton de rafraîchissement | 30 jours | cookie REFRESH_TOKEN (HttpOnly, limité au chemin /api/token) ; table refresh_tokens |
Le cookie est prioritaire ; l'en-tête sert aux scripts et aux outils en ligne de commande. En HTTPS, les cookies sont Secure et SameSite=Strict.
Routes
| Route | Rôle |
|---|---|
POST /api/login | Connexion (email, password) ; renvoie le jeton, le jeton de rafraîchissement et le profil, ou requires2FA si la double authentification est active pour le compte |
POST /api/2fa/verify | Seconde étape de connexion |
POST /api/token/refresh | Nouveau jeton d'accès ; le jeton de rafraîchissement est remplacé à chaque usage |
POST /api/token/invalidate | Déconnexion : supprime le jeton de rafraîchissement et efface les cookies |
Le frontend rafraîchit le jeton avant chaque requête quand il est absent de la mémoire (rechargement de page), expiré ou proche de l'expiration, et aussi après une réponse 401.
Protection CSRF
Quand la requête est authentifiée par cookie, toute mutation (POST, PUT, PATCH, DELETE) doit porter l'en-tête X-CSRF-TOKEN, égal au cookie XSRF-TOKEN (motif « double submit cookie »). Les appels authentifiés par en-tête Authorization n'en ont pas besoin. Désactivable en développement seulement (CSRF_ENABLED=false).
Clés
| Élément | Valeur |
|---|---|
| Emplacement | ~/pch-sig/backend/config/jwt/ sur le serveur, monté sur /app/config/jwt du conteneur backend : en lecture seule en production, en écriture en préproduction |
| Passphrase | JWT_PASSPHRASE dans .env.prod |
| Création | l'entrypoint du backend sait générer une paire RSA 4096 si elles manquent ou si la passphrase ne les ouvre pas, à condition que le répertoire soit inscriptible (préproduction, développement) ; en production, les clés se créent sur l'hôte |
La passphrase de .env.prod et les clés de l'hôte vont ensemble. Pour changer l'une, changer l'autre :
cd ~/pch-sig/backend/config/jwt
openssl genpkey -out private.pem -aes256 -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -pass env:JWT_PASSPHRASE
openssl pkey -in private.pem -out public.pem -pubout -passin env:JWT_PASSPHRASE
sudo chown 82:82 private.pem public.pem # 82 = www-data dans l'image php:8.3-fpm-alpine
sudo chmod 600 private.pem && sudo chmod 644 public.pem
(JWT_PASSPHRASE exportée dans le shell le temps de la commande, puis reportée dans .env.prod.) Recréer ensuite le conteneur backend (up -d --force-recreate --no-deps backend). Les utilisateurs ne sont pas déconnectés : le jeton de rafraîchissement ne dépend pas de la clé, le premier appel répond 401, le frontend rafraîchit et rejoue la requête. Procédure complète de rotation des secrets : docs/ROTATION_SECRETS.md du dépôt.
Obtenir un jeton en ligne de commande
curl -s -X POST https://sig-preprod.ucp-pch.org/api/login \
-H "Content-Type: application/json" \
-d '{"email":"<EMAIL>","password":"<MOT_DE_PASSE>"}' > token.json
curl -s https://sig-preprod.ucp-pch.org/api/users/me/permissions \
-H "Authorization: Bearer $(jq -r .token token.json)"
Ne jamais coller un jeton ni un mot de passe dans un ticket, un journal ou une conversation ; supprimer token.json après usage.
Dépannage
| Symptôme | Cause | Action |
|---|---|---|
| 500 à la connexion, « Unable to create a signed JWT » | clé privée illisible par www-data, ou passphrase qui ne correspond pas aux clés | sur l'hôte, propriétaire 82:82 et mode 600 pour private.pem ; sinon vérifier la passphrase |
| Tous les utilisateurs déconnectés | table refresh_tokens vidée, ou cookies effacés | se reconnecter |
| 401 en boucle | horloge du serveur décalée, ou cookie bloqué par le navigateur | docker exec pch_backend date ; vérifier que le site est servi en HTTPS |
| 403 « CSRF » sur une mutation | en-tête X-CSRF-TOKEN absent ou périmé | recharger la page (le cookie XSRF-TOKEN est réémis) |
docker exec -u www-data pch_backend php bin/console debug:config lexik_jwt_authentication