Configuration Keycloak
Version 1, 29 septembre 2026. Livrable n° 4 des travaux de conception du Lot 0. Applique les décisions « Fournisseur d’identité », « Enrôlement », « Session hors-ligne », « Déverrouillage local », « Révocation » et « Stockage du jeton » (modifiée par EC-033).
- Un seul royaume
biotrace. Chaque client de BioTrace est une organisation Keycloak, liée àapp.organisation. - Keycloak authentifie, la base autorise. Keycloak dit qui est l’utilisateur et à quelle organisation il appartient. Les permissions fines et la séparation des tâches sont dans la base et dans
packages/domain. - Trois clients : back-office, application terrain, API.
- Jeton d’accès de 5 minutes. Session hors-ligne de 30 jours d’inactivité pour l’application terrain.
- Le jeton PowerSync n’est pas émis par Keycloak mais par l’API, après vérification de l’appareil.
1. Version et déploiement
Section intitulée « 1. Version et déploiement »- Keycloak 26 ou supérieur (organisations disponibles), en conteneur sur le cluster Kubernetes, base PostgreSQL dédiée (distincte de la base métier). Développement : image 26.0.5, épinglée dans
infra/docker-compose.yml. - Configuration versionnée dans
infra/keycloak/(realm/biotrace.json), appliquée par keycloak-config-cli dans le pipeline (ADR 0034). Aucune modification manuelle en production. Mode d’emploi :infra/keycloak/README.md. - Thème de connexion aux couleurs du design system « Registre » (Lot 7, facultatif pour le pilote).
2. Royaume biotrace
Section intitulée « 2. Royaume biotrace »| Réglage | Valeur | Commentaire |
|---|---|---|
| Organisations | Activées | Une organisation Keycloak par client BioTrace |
| Langues | français (défaut), anglais | |
| Inscription libre | Désactivée | Comptes créés par l’administrateur client ou par l’API |
| Connexion par e-mail | Désactivée | Beaucoup d’agents n’ont pas d’e-mail. Identifiant : code agent ou numéro de téléphone |
| Vérification de l’e-mail | Désactivée | |
| Réinitialisation du mot de passe | Par l’administrateur uniquement | Pas de lien par e-mail |
| Politique de mot de passe | 12 caractères minimum, différent de l’identifiant, historique de 5 | |
| Protection contre la force brute | Activée : verrouillage temporaire après 5 échecs | |
| Événements de connexion et d’administration | Enregistrés, conservés 1 an | Exportés vers la supervision (Lot 7) |
2.1 Durées de session
Section intitulée « 2.1 Durées de session »| Réglage Keycloak | Valeur | Source |
|---|---|---|
| Durée du jeton d’accès | 5 minutes | Découpage |
| Session SSO, inactivité | 30 minutes | Proposition, back-office |
| Session SSO, durée maximale | 10 heures | Proposition, une journée de travail |
| Session hors-ligne, inactivité | 30 jours | Découpage |
| Session hors-ligne, durée maximale | Activée, 180 jours | Proposition : impose une reconnexion complète au moins deux fois par an |
| Rotation des jetons de rafraîchissement | Activée, réutilisation interdite | Un jeton volé et rejoué est détecté |
Côté application terrain, et non dans Keycloak : saisie hors-ligne limitée à 7 jours, verrouillage local après 5 minutes d’inactivité, déverrouillage par code PIN ou empreinte digitale.
3. Organisations
Section intitulée « 3. Organisations »Remplacée en partie par la page Keycloak — fonctionnement multi-clients : un utilisateur peut appartenir à plusieurs organisations (comptes externes) ; l’organisation active est choisie par requête.
- Chaque organisation Keycloak porte l’attribut
biotrace_organisation_id, égal àapp.organisation.id. La colonneapp.organisation.keycloak_organisation_idfait le lien inverse. - Un utilisateur appartient à une seule organisation au pilote. Les auditeurs multi-organisations (portail auditeur, Lot 5) feront l’objet d’une décision séparée.
- Pour les grands clients qui ont leur propre annuaire, un fournisseur d’identité externe (OpenID Connect ou SAML) peut être rattaché à leur organisation. Leurs utilisateurs se connectent alors avec leur compte d’entreprise.
4. Clients
Section intitulée « 4. Clients »| Client | Type | Flux | Particularités |
|---|---|---|---|
biotrace-backoffice |
Public | Code d’autorisation + PKCE (S256) | Redirections limitées aux domaines du back-office ; pas de portée offline_access |
biotrace-cockpit |
Public | Code d’autorisation + PKCE (S256) | Cockpit de l’éditeur (ADR 0044) : redirections limitées à COCKPIT_URL ; audience biotrace-api ; pas de portée offline_access. Les rôles editeur_* sont lus par l’API, pas par le client |
biotrace-terrain |
Public | Code d’autorisation + PKCE (S256) | Portée offline_access autorisée ; redirection par schéma d’application Android fr.biotrace.terrain:/oauth2redirect |
biotrace-api |
Confidentiel, sans flux de connexion | – | Audience des jetons ; compte de service pour l’API d’administration (création d’utilisateurs, révocation de sessions) |
4.1 Contenu des jetons
Section intitulée « 4.1 Contenu des jetons »| Revendication | Source | Usage |
|---|---|---|
sub |
Keycloak | Lien avec app.utilisateur.keycloak_sub |
organization |
Mappeur d’appartenance aux organisations | L’API en déduit organisation_id |
aud |
Mappeur d’audience biotrace-api |
Vérifiée par l’API |
acr / amr |
Keycloak | Indique si le second facteur a été utilisé |
preferred_username |
Keycloak | Affichage |
Aucune permission métier dans le jeton. L’API lit les ensembles de permissions en base, à chaque requête, avec un cache court. Une permission retirée prend effet sans attendre l’expiration d’un jeton.
4.2 Extrait de configuration
Section intitulée « 4.2 Extrait de configuration »Extrait de présentation. Le fichier de référence est
infra/keycloak/realm/biotrace.json, appliqué et testé (section 10).
{ "realm": "biotrace", "enabled": true, "organizationsEnabled": true, "defaultLocale": "fr", "supportedLocales": ["fr", "en"], "registrationAllowed": false, "loginWithEmailAllowed": false, "verifyEmail": false, "resetPasswordAllowed": false, "bruteForceProtected": true, "failureFactor": 5, "passwordPolicy": "length(12) and notUsername and passwordHistory(5)", "accessTokenLifespan": 300, "ssoSessionIdleTimeout": 1800, "ssoSessionMaxLifespan": 36000, "offlineSessionIdleTimeout": 2592000, "offlineSessionMaxLifespanEnabled": true, "offlineSessionMaxLifespan": 15552000, "revokeRefreshToken": true, "refreshTokenMaxReuse": 0, "eventsEnabled": true, "adminEventsEnabled": true, "clients": [ { "clientId": "biotrace-terrain", "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["fr.biotrace.terrain:/oauth2redirect"], "attributes": { "pkce.code.challenge.method": "S256" }, "optionalClientScopes": ["offline_access"] }, { "clientId": "biotrace-backoffice", "publicClient": true, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "redirectUris": ["https://app.biotrace.example/*"], "webOrigins": ["https://app.biotrace.example"], "attributes": { "pkce.code.challenge.method": "S256" } }, { "clientId": "biotrace-api", "publicClient": false, "standardFlowEnabled": false, "serviceAccountsEnabled": true } ]}Les domaines sont des exemples, à remplacer par environnement (développement, recette, production).
5. Second facteur
Section intitulée « 5. Second facteur »Désactivé pour le moment (EC-134, 04/10/2026). Les sous-flux TOTP du royaume sont en
DISABLED: tout le monde se connecte par mot de passe seul. Groupe, rôle et mise à jour du groupe par l’API sont conservés ; la réactivation consiste à remettre les deux sous-flux enCONDITIONALdansbiotrace.json, puisappliquer.sh. À réactiver avant la mise en service.
- Disponible pour tous : code à usage unique par application d’authentification (TOTP).
- Exigé pour les utilisateurs du groupe Keycloak
second_facteur_exige. - L’API tient ce groupe à jour : un utilisateur y est ajouté dès qu’on lui attribue un ensemble de permissions marqué
sensible(app.ensemble_permissions.sensible), et retiré quand il n’en a plus. - Ensembles sensibles par défaut : administrateur client, direction, RSCI, comptable. Les membres de l’équipe de l’éditeur ont le second facteur par leur compte.
- Le flux de connexion
biotrace-browserimpose le TOTP par une condition sur un rôle : Keycloak n’a pas de condition « appartenance au groupe », donc le groupesecond_facteur_exigeporte le rôle du même nom, et la condition « rôle de l’utilisateur » le lit. Ajouter un utilisateur au groupe suffit. Un second sous-flux demande le TOTP à tout utilisateur qui en a configuré un (second facteur disponible pour tous). - Pour les agents de terrain, le second facteur n’est demandé qu’à l’enrôlement et aux reconnexions complètes. Au quotidien, le code PIN local suffit.
6. Rôles métier et séparation des tâches
Section intitulée « 6. Rôles métier et séparation des tâches »Keycloak ne porte que les rôles de l’éditeur BioTrace (EC-099, depuis le 02/10/2026 ; ils remplacent equipe_biotrace). Tout le reste est en base.
| Rôle | Périmètre |
|---|---|
editeur_admin |
Super administrateur : tous les droits de l’éditeur, en global et pour chaque client ; passe toutes les gardes de rôle éditeur |
editeur_parametrage |
Paramétrage global : packs filière et référentiels, catalogues, publication |
editeur_support |
Paramétrage et assistance pour un client : ouverture des comptes clients ; accès journalisé à un client (RG-309, à venir) |
L’équipe de l’éditeur n’est pas soumise à la séparation des tâches : celle-ci vaut pour les clients (EC-012).
Ensembles de permissions livrés par défaut à chaque nouvelle organisation, modifiables par l’administrateur client :
| Ensemble | Principales permissions |
|---|---|
| Agent collecteur | Commandes de terrain du Lot 3 |
| Inspecteur | Commandes d’inspection du Lot 4, lecture du périmètre |
| Magasinier | Réceptions, lots, emplacements |
| Responsable transformation | Transformations, rendements |
| Commercial | Ventes, expéditions, contrats de vente |
| Comptable | Paiements, avances |
| RSCI | Contrôles, capacités (validation), non-conformités, comité |
| Direction | Capacités (validation), tableaux de bord |
| Administrateur client | Utilisateurs, appareils, paramétrage, ensembles de permissions |
Contraintes figées, qui ne dépendent pas des ensembles : voir packages/domain, module droits, et les déclencheurs de la base (auteur différent du validateur, validateurs distincts, auteur d’une commande différent de celui qui lève le contrôle, conflit d’intérêts de l’inspecteur).
7. Enrôlement d’un appareil
Section intitulée « 7. Enrôlement d’un appareil »- L’administrateur client crée le compte de l’agent (par l’API, qui crée l’utilisateur dans Keycloak et dans
app.utilisateur). - Il génère un code d’enrôlement dans le back-office, valable 24 heures. Seule son empreinte est conservée en base.
- Au bureau, avec du réseau, l’agent ouvre l’application terrain et se connecte : navigateur système, code d’autorisation + PKCE, portée
offline_access, second facteur si exigé. - L’application stocke le jeton de rafraîchissement hors-ligne dans le stockage sécurisé Android (Keystore, via Capacitor, EC-033).
- L’agent saisit le code d’enrôlement.
POST /v1/appareils/enrolercrée l’appareil et renvoie son code court et la séquence de départ. - L’agent définit son code PIN. L’empreinte digitale peut être activée ensuite.
8. Jeton PowerSync
Section intitulée « 8. Jeton PowerSync »- L’application appelle
POST /v1/sync/jetonavec son jeton d’accès Keycloak. - L’API vérifie le jeton, l’utilisateur, et le statut de l’appareil.
- Elle signe un jeton de 5 minutes, avec sa propre clé, portant
organisation_id,utilisateur_idetappareil_id. - Le service PowerSync valide ce jeton avec la clé publique de l’API, publiée à une adresse JWKS interne au cluster.
Keycloak n’a donc pas besoin de connaître les appareils.
9. Révocation
Section intitulée « 9. Révocation »| Situation | Action | Effet |
|---|---|---|
| Téléphone perdu ou volé | Révocation de l’appareil dans le back-office | L’API révoque la session hors-ligne de l’agent pour biotrace-terrain (API d’administration Keycloak) et refuse tout nouveau jeton PowerSync pour cet appareil |
| Départ d’un agent | Désactivation de l’utilisateur | Toutes ses sessions sont révoquées ; ses appareils sont révoqués |
| Suspicion de compromission du compte | Révocation de toutes les sessions + réinitialisation du mot de passe | Reconnexion complète obligatoire |
Sur le téléphone, à la reconnexion : envoi de la file de commandes, puis effacement des données locales (protocole de synchronisation, section 12).
Limite connue. Tant qu’il reste hors-ligne, un téléphone révoqué continue de fonctionner jusqu’à la limite de 7 jours de saisie. C’est le compromis retenu par le découpage. Le code PIN et le verrouillage local en limitent l’exposition.
10. Critères de recette et état d’exécution
Section intitulée « 10. Critères de recette et état d’exécution »Les tests sont dans infra/keycloak/test/ (pnpm infra:keycloak:tester, job keycloak de la CI). Ils tournent sur un royaume de test aux durées réduites (jeton d’accès de 4 s, inactivité hors-ligne de 8 s), et un test statique vérifie les vraies valeurs (300 s ; 30 jours ; 180 jours).
| id | Scénario | Résultat attendu | État au 30/09/2026 |
|---|---|---|---|
| KC-01 | Connexion au back-office d’un utilisateur RSCI sans TOTP configuré | Configuration du TOTP imposée | Exécuté |
| KC-02 | Jeton d’accès après 5 minutes | Refusé par l’API ; renouvelé par le jeton de rafraîchissement | Exécuté côté Keycloak (expiration, renouvellement) ; refus par l’API exécuté (apps/api/test/identite/garde.test.ts) |
| KC-03 | Application terrain hors-ligne 29 jours, puis réseau | Session renouvelée sans mot de passe | Exécuté (durée réduite) ; valeur réelle vérifiée |
| KC-04 | Hors-ligne 31 jours, puis réseau | Reconnexion complète exigée ; file de commandes conservée et envoyée après reconnexion | Reconnexion exécutée (durée réduite) ; conservation de la file : application terrain |
| KC-05 | Appareil révoqué, demande de jeton PowerSync | Refus | Exécuté (apps/api/test/identite/identite.test.ts, après révocation par l’API) |
| KC-06 | Jeton de rafraîchissement rejoué | Refus et révocation de la session | Exécuté |
| KC-07 | Retrait d’une permission pendant une session | Refus dès la requête suivante | Exécuté (garde.test.ts, identite.test.ts) : les ensembles sont lus en base à chaque requête, sans cache |
| KC-08 | Utilisateur d’une organisation A appelant l’API avec des identifiants de l’organisation B | Aucune donnée (RLS) | Couvert en base : SB-03 |
| KC-09 | Enrôlement avec un code expiré ou déjà utilisé | Refus | En attente : l’enrôlement (POST /v1/appareils/enroler) reste à écrire |
Les scénarios KC-10 à KC-19 de la page multi-clients suivent le même principe : KC-10, KC-11, KC-14, KC-15, KC-18 et KC-19 sont exécutés par les tests du module identite (apps/api/test/identite/, vraie base, faux Keycloak en mémoire, plus l’adaptateur réel contre Keycloak dans keycloak-reel.test.ts), KC-13 par infra/keycloak/test/, KC-17 est couvert par SB-16. KC-12 et KC-16 (comptes externes) attendent l’invitation et l’acceptation des comptes ext. ; KC-09, l’enrôlement. Ces scénarios sont écrits dans kc-en-attente.test.mjs, sautés avec leur motif.
Constat sur le compte de service (EC-042) : sur Keycloak 26.x, gérer les organisations exige manage-realm, qui permet aussi de modifier le royaume. Décision (EC-042, option 2) : le compte biotrace-api garde les droits sur les utilisateurs et les groupes ; un second compte de service, biotrace-api-organisations, porte seul manage-realm (voir la page « Keycloak multi-clients », §6.2).