Intégration SSO — récupération des droits géographiques
Cette page décrit précisément comment VegLab récupère les droits spatiaux d'un utilisateur connecté via une source externe (Lobelia), et comment l'appel à l'API de la source est authentifié.
Principe
Le token OIDC reçu par l'API VegLab ne contient pas les droits : il porte seulement une référence, à savoir l' URL d'un endpoint de la source (façon OIDC Distributed Claims). VegLab appelle cet endpoint une fois par connexion pour récupérer les contraintes spatiales (communes, départements, etc.) et les persiste dans l'entité UserRight.
L'appel à l'endpoint de la source est authentifié par le token que la source a elle-même émis lors de l'authentification. La source valide donc ses propres tokens (aucune logique de validation spécifique à écrire de son côté) et en déduit l'utilisateur de façon prouvée — il n'y a ni secret partagé, ni e-mail déclaratif dans cet appel.
Le point clé : où VegLab trouve-t-il le token de la source ?
VegLab ne reçoit jamais le token de la source : le front se connecte à Keycloak et reçoit un token Keycloak (audience veglab-web). C'est Keycloak qui, en tant que broker OIDC, a obtenu et stocké le token de la source pendant le login (option storeToken de l'IdP).
VegLab récupère ce token stocké via l' endpoint broker token de Keycloak, en présentant le token Keycloak de l'utilisateur (celui qu'il a déjà en main, le Bearer de la requête entrante) :
Où {alias} est l'alias de l'IdP Keycloak (configuré côté VegLab dans le claim_map, pas codé en dur). Keycloak vérifie le token Keycloak, contrôle que l'utilisateur possède le rôle client broker → read-token, rafraîchit le token de la source si nécessaire, puis le restitue.
Séquence complète d'un login
Garanties et garde-fous (côté VegLab)
Once-per-login: la synchronisation s'exécute une seule fois par session, repérée via le claim
sid(UserRightSynchronizer::synchronize()), bien que le firewall soit stateless.Allowlist anti-SSRF: l'URL extraite du claim n'est appelée que si son hôte figure dans
VEGLAB_USER_RIGHTS_ALLOWED_HOSTS— variable par environnement, injectée par compose depuis le.env.<env>actif (détail dansdocs/PERMISSIONS.md). Un hôte manquant se traduit par un refus d'authentification, pas par une session sans droits.Fail-closed: si le token de la source ne peut être récupéré (rôle
read-tokenmanquant, absence de token stocké, endpoint injoignable…), l'authentification est refusée — on ne laisse pas entrer un utilisateur sans les droits qu'on est censé faire respecter.Découplage: aucun nom de système externe n'apparaît dans le code ; l'alias du broker et la correspondance claim → (type, role) sont en configuration (
config/services.yaml,app.user_rights.claim_map).
Prérequis de configuration Keycloak (realm veglab)
Sur l'IdP source (alias lobelia) :
storeToken: true— sans quoi Keycloak jette le token de la source et l'endpoint broker token ne renvoie rien.addReadTokenRoleOnCreate: true— accorde le rôlebroker/read-tokenaux nouveaux utilisateurs brokerés. Les utilisateurs déjà existants doivent se voir attribuer ce rôle manuellement.Le
scopedu token émis par la source doit autoriser l'endpoint des droits.
Les deux mappers, à ne pas confondre
Le claim traverse Keycloak en deux étapes, portées par deux objets distincts que la console d'administration nomme presque pareil. C'est le second qui manque, en général, quand le claim n'apparaît pas dans le token alors que « le mapper est bien là ».
Objet | Où il se configure | Ce qu'il fait |
|---|---|---|
Identity Provider Mapper (Attribute Importer) | Identity providers → | claim de la source → attribut utilisateur |
Protocol mapper (User Attribute) | Clients → | attribut utilisateur → claim |
Le premier ne touche pas au token que reçoit VegLab : il remplit un attribut sur le compte Keycloak. Le second est celui qui écrit le claim. Valeurs exactes des champs : checklist d'installation (INSTALL.md, section 8).
Deux effets retardés à connaître : le syncMode: FORCE de l'importer ne réimporte l'attribut qu'au prochain login brokeré, et Keycloak lit read-token dans le token présenté, pas en base — dans les deux cas, il faut se reconnecter pour observer le changement.
Dépannage
Le symptôme désigne le maillon, parce que les deux moitiés de la chaîne échouent de façon opposée.
Claim absent du token. Le synchronizer ne trouve rien à synchroniser, ne logue rien, et l'utilisateur entre sans aucune contrainte: faute de UserRight, le voter considère que la restriction ne le concerne pas et autorise tout. Un droit spatial qui « ne marche pas » est donc toujours plus permissif, jamais plus restrictif. Sa signature en base :
rights_synced_sid renseigné et aucune ligne vl_user_right: la synchronisation est allée au bout sans jamais voir le claim (le sid n'est écrit qu'en toute fin de parcours, et seulement si rien n'a échoué).
Tout le reste est fail-closed: 401, « Nous n'avons pas pu récupérer vos droits dans le SI source ». Le log applicatif (docker logs vl-api, filtré sur UserRight) dit lequel :
Message | Maillon en cause |
|---|---|
| l'hôte de l'URL du claim n'est pas dans |
| le rôle |
| issuer : Keycloak n'a pas reconstruit son URL publique ( |
| la source a répondu hors 2xx |