Aller au contenu

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.
  • 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).
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)
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.

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 colonne app.organisation.keycloak_organisation_id fait 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.
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)
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.

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).

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 en CONDITIONAL dans biotrace.json, puis appliquer.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-browser impose le TOTP par une condition sur un rôle : Keycloak n’a pas de condition « appartenance au groupe », donc le groupe second_facteur_exige porte 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.

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).

  1. L’administrateur client crée le compte de l’agent (par l’API, qui crée l’utilisateur dans Keycloak et dans app.utilisateur).
  2. Il génère un code d’enrôlement dans le back-office, valable 24 heures. Seule son empreinte est conservée en base.
  3. 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é.
  4. L’application stocke le jeton de rafraîchissement hors-ligne dans le stockage sécurisé Android (Keystore, via Capacitor, EC-033).
  5. L’agent saisit le code d’enrôlement. POST /v1/appareils/enroler crée l’appareil et renvoie son code court et la séquence de départ.
  6. L’agent définit son code PIN. L’empreinte digitale peut être activée ensuite.
  • L’application appelle POST /v1/sync/jeton avec 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_id et appareil_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.

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.

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).