Le modèle de Keycloak
Les objets de Keycloak (realm, client, utilisateur, groupe, rôles, client scopes, protocol mappers, user profile) et la façon dont ils décident du contenu des jetons.
Sur cette page
- Le realm, unité d’isolement
- Les clients : les applications vues par Keycloak
- Utilisateurs, groupes et rôles
- Ce qui part dans le jeton : client scopes et protocol mappers
- Le user profile : déclarer les attributs
- Console d’administration et console de compte
- Ce qu’il faut retenir pour l’architecture
- Natif, extension ou hébergeur
- Quiz
Vous connaissez OIDC et SAML. Keycloak les implémente avec un modèle d’objets qui lui est propre, et c’est ce modèle que vous manipulerez à chaque décision : où vit un utilisateur, comment un droit est rangé, ce qui décide qu’une information part ou non dans un jeton. Ce cours pose ce vocabulaire. Les cours suivants s’appuient dessus pour trancher le découpage en realms, les sources d’utilisateurs et les parcours d’authentification.
Le realm, unité d’isolement
Un realm est un espace d’identité autonome. Il contient ses utilisateurs, ses clients, ses rôles, ses groupes, ses parcours d’authentification, ses clés de signature, sa politique de mots de passe et son thème. Un utilisateur appartient à un seul realm et une session SSO ne vaut que dans son realm.
Chaque realm expose ses propres points de terminaison. L’émetteur (iss) des jetons est l’URL du realm, par exemple https://idp.example.org/realms/agents. Une application raccordée à un realm ne voit rien des autres.
Le realm master est particulier : il sert à administrer l’instance et les autres realms. On n’y met pas d’utilisateurs métier. Le choix du nombre de realms est traité dans le cours suivant.
Les clients : les applications vues par Keycloak
Un client est une application (ou une API) qui délègue l’authentification à Keycloak. Il a un protocole, OIDC ou SAML, et un identifiant (clientId en OIDC, l’entity ID en SAML).
En OIDC, un client est soit public (application monopage, application mobile : il ne peut pas garder de secret), soit confidentiel (application serveur, qui s’authentifie auprès de Keycloak par secret, clé privée signée ou certificat). Un client confidentiel peut aussi disposer d’un service account, un compte technique utilisé avec le flux client credentials.
Le client porte ses propres réglages : URI de redirection autorisées, durées de session spécifiques, thème de connexion, surcharge de parcours d’authentification, niveau d’authentification minimal. Ces réglages par client évitent souvent de créer un realm, comme on le verra au cours 2.
Utilisateurs, groupes et rôles
Un utilisateur a un identifiant technique (UUID, qui devient le sub des jetons OIDC), un nom d’utilisateur, un e-mail, des attributs et des credentials (mot de passe, OTP, clé WebAuthn).
Les rôles expriment des droits :
- un rôle de realm a un sens pour tout le realm (par exemple
agent,usager) ; - un rôle de client n’a de sens que pour une application (par exemple
editeursur le clientportail-agents) ; - un rôle composite contient d’autres rôles, de realm ou de client. Affecter le composite revient à affecter tout ce qu’il contient.
Les groupes sont hiérarchiques. Un utilisateur membre d’un groupe hérite des rôles affectés au groupe et à ses groupes parents. Les groupes peuvent aussi porter des attributs, que certains mappers et conditions savent lire. Les groupes servent à organiser des populations (une direction, un service, un partenaire) et à porter des rôles en masse.
| Objet | Portée | Usage typique | Dans le jeton OIDC par défaut |
|---|---|---|---|
| Rôle de realm | tout le realm | profil transverse : agent, usager, administrateur | realm_access.roles |
| Rôle de client | un client | droit applicatif : lecteur, éditeur, valideur | resource_access.<client>.roles |
| Rôle composite | selon ses composants | profil métier qui regroupe plusieurs droits | les rôles effectifs, dépliés |
| Groupe | hiérarchie du realm | structure organisationnelle, affectation en masse | absent sans mapper dédié |
| Attribut utilisateur | un utilisateur | donnée d’identité : matricule, service | absent sans mapper dédié |
Chaque realm a aussi un rôle composite default-roles-<realm>, affecté à tout nouvel utilisateur. Le détail du RBAC et de l’héritage par les groupes fait l’objet du cours 5.
Ce qui part dans le jeton : client scopes et protocol mappers
Un protocol mapper transforme une donnée du modèle (rôle, attribut, groupe, propriété de session) en claim OIDC ou en attribut SAML. Un client scope regroupe des mappers et des rôles sous un nom réutilisable : profile, email, roles, acr, ou les vôtres.
Un client scope est rattaché à un client de deux façons :
- default : appliqué à chaque demande de jeton du client ;
- optional : appliqué seulement si l’application le demande dans le paramètre
scopede la requête OIDC.
Deux réglages limitent ensuite les rôles inclus :
- Full scope allowed (actif par défaut) place dans le jeton tous les rôles de l’utilisateur, quel que soit le client ;
- une fois ce réglage désactivé, les role scope mappings du client et de ses client scopes définissent les rôles autorisés à apparaître.
Voici un access token produit pour un utilisateur qui a le rôle de realm composite agent (contenant le rôle de client lecteur) et qui appartient au groupe instructeurs (qui porte le rôle de client editeur), avec un client scope optional agent-info demandé :
{
"iss": "http://localhost:8080/realms/academy",
"aud": "account",
"sub": "f1fd4eab-20fb-4c3b-b814-b733d1a81b70",
"azp": "portail-agents",
"acr": "1",
"scope": "openid agent-info profile email",
"realm_access": { "roles": ["agent", "offline_access", "default-roles-academy", "uma_authorization"] },
"resource_access": {
"portail-agents": { "roles": ["lecteur", "editeur"] },
"account": { "roles": ["manage-account", "manage-account-links", "view-profile"] }
},
"matricule": "A123456",
"preferred_username": "jdupont",
"email": "jean.dupont@example.org"
}
Le rôle lecteur vient du composite, editeur du groupe, matricule du mapper porté par le client scope. Si l’application n’avait pas demandé agent-info, le claim matricule serait absent.
Le mapper se crée ainsi :
kcadm.sh create client-scopes/$SCOPE_ID/protocol-mappers/models -r academy \
-s name=matricule -s protocol=openid-connect \
-s protocolMapper=oidc-usermodel-attribute-mapper \
-s 'config."user.attribute"=matricule' -s 'config."claim.name"=matricule' \
-s 'config."jsonType.label"=String' \
-s 'config."access.token.claim"=true' -s 'config."id.token.claim"=true'
Pour un besoin que les mappers fournis ne couvrent pas (un claim calculé à partir d’un référentiel externe, par exemple), il faut écrire un protocol mapper en Java. C’est une extension, avec son coût de maintenance à chaque montée de version.
Le user profile : déclarer les attributs
Depuis Keycloak 24, le user profile est supporté et activé par défaut. Chaque realm déclare en JSON les attributs qu’il gère. Pour chacun :
- des validations : longueur, motif (expression régulière), e-mail, URI, liste de valeurs, date, entier, etc. ;
- des permissions : qui peut le voir (
view) et le modifier (edit), parmiadminetuser; - une condition required : obligatoire pour les administrateurs, pour l’usager, ou seulement quand un client scope donné est demandé ;
- des annotations pour l’affichage (type de champ, aide) et un groupe d’attributs pour structurer les formulaires.
{
"name": "matricule",
"displayName": "Matricule",
"validations": {
"pattern": {
"pattern": "^[A-Z][0-9]{6}$",
"error-message": "Format attendu : une lettre et six chiffres"
}
},
"permissions": { "view": ["admin", "user"], "edit": ["admin"] },
"multivalued": false
}
Ces règles s’appliquent partout : console d’administration, API REST, console de compte, formulaires d’inscription et de mise à jour du profil. Un usager ne peut pas contourner une permission edit: admin en appelant l’API de compte.
Les attributs non déclarés sont des attributs non gérés. Par défaut, ils sont refusés. La politique du realm peut les autoriser partout, ou seulement en lecture ou en écriture pour les administrateurs. Une migration depuis une version antérieure à 24 est le cas typique où cette politique compte : des attributs posés par des scripts existants peuvent devenir invisibles ou être rejetés.
Console d’administration et console de compte
La console d’administration (/admin/<realm>/console) permet de tout configurer. Les droits d’administration sont des rôles du client realm-management de chaque realm : manage-users, view-users, manage-clients, view-events, etc. On peut donc donner à une équipe la gestion des utilisateurs d’un realm sans lui ouvrir le reste. Les permissions fines (déléguer la gestion d’un groupe seulement) sont traitées dans le cours 8.
La console de compte (/realms/<realm>/account) est ce que voit l’usager :
| Rubrique | Ce que l’usager fait | Ce qui la pilote |
|---|---|---|
| Informations personnelles | voir et modifier ses attributs | permissions du user profile |
| Connexion | changer son mot de passe, ajouter ou retirer un OTP, une clé WebAuthn | required actions activées, parcours d’authentification |
| Activité des appareils | voir ses sessions, se déconnecter à distance | sessions du realm |
| Comptes liés | lier ou délier un fournisseur d’identité externe | identity providers du realm |
| Applications | voir les applications utilisées, révoquer un consentement | clients et consentements |
La suppression de compte par l’usager existe mais est désactivée par défaut : il faut activer la required action Delete Account et donner le rôle delete-account.
Ce qu’il faut retenir pour l’architecture
Trois questions reviennent sans cesse dans un projet Keycloak, et le modèle y répond :
- Où est rangé ce droit ? Rôle de realm s’il est transverse, rôle de client s’il est applicatif, porté par un groupe s’il suit l’organisation.
- Qui a besoin de le voir dans un jeton ? Un client scope, default ou optional, et Full scope allowed désactivé.
- D’où vient cette donnée et qui la modifie ? Le user profile pour les règles, la fédération pour la source (cours 3).
Construire le modèle et lire le jeton
25 min
Installer le lab- Depuis le dossier
keycloak-academy-lab/, ouvrez une session kcadm dans le conteneur Keycloak. Les commandes suivantes supposent la fonction shell définie ici.
kcadm() { docker compose exec -T keycloak /opt/keycloak/bin/kcadm.sh "$@"; }
kcadm config credentials --server http://localhost:8080 --realm master --user admin --password admin- Créez le realm
academy(s’il existe déjà, l’erreur est sans conséquence), un client confidentiel et ses rôles.
kcadm create realms -s realm=academy -s enabled=true
CID=$(kcadm create clients -r academy -s clientId=portail-agents -s publicClient=false \
-s 'redirectUris=["http://localhost:3000/*"]' -i)
kcadm create roles -r academy -s name=agent
kcadm create clients/$CID/roles -r academy -s name=lecteur
kcadm create clients/$CID/roles -r academy -s name=editeur
kcadm add-roles -r academy --rname agent --cclientid portail-agents --rolename lecteur- Créez un groupe
instructeursportant le rôleediteur, puis un utilisateur membre du groupe avec le rôleagent.
GID=$(kcadm create groups -r academy -s name=instructeurs -i)
kcadm add-roles -r academy --gid $GID --cclientid portail-agents --rolename editeur
USER_ID=$(kcadm create users -r academy -s username=jdupont -s email=jean.dupont@example.org \
-s firstName=Jean -s lastName=Dupont -s enabled=true -i)
kcadm update users/$USER_ID/groups/$GID -r academy -n
kcadm add-roles -r academy --uusername jdupont --rolename agent- Générez un access token d’exemple sans passer par une connexion, et repérez d’où vient chaque rôle.
kcadm get clients/$CID/evaluate-scopes/generate-example-access-token -r academy \
-q scope=openid -q userId=$USER_ID- Dans la console (http://localhost:8080/admin, realm
academy), ouvrez Realm settings, onglet User profile, et créez l’attributmatriculeavec un validateur pattern^[A-Z][0-9]{6}$, visible par admin et user, modifiable par admin seulement. - Vérifiez la validation : la première commande doit échouer avec votre message d’erreur, la seconde réussir.
kcadm update users/$USER_ID -r academy -s 'attributes.matricule=["X12"]'
kcadm update users/$USER_ID -r academy -s 'attributes.matricule=["A123456"]'- Créez un client scope
agent-info, ajoutez-lui le mappermatricule(commande de la section sur les mappers, avec$SCOPE_ID), rattachez-le en optional au client, puis comparez les jetons avec et sans ce scope.
SCOPE_ID=$(kcadm create client-scopes -r academy -s name=agent-info -s protocol=openid-connect -i)
# créer ici le mapper matricule sur client-scopes/$SCOPE_ID
kcadm update clients/$CID/optional-client-scopes/$SCOPE_ID -r academy
kcadm get clients/$CID/evaluate-scopes/generate-example-access-token -r academy \
-q "scope=openid agent-info" -q userId=$USER_ID- Désactivez Full scope allowed sur
portail-agents(dans la console : onglet Client scopes du client, scope dédié, onglet Scope), puis régénérez le jeton.realm_accesset les rôles du clientaccountdisparaissent ; seuls restent les rôles du client lui-même.
kcadm update clients/$CID -r academy -s fullScopeAllowed=falseNatif, extension ou hébergeur
Natif : fourni par Keycloak, à configurer. Extension : demande du code à maintenir (un SPI, le mécanisme de plugin de Keycloak). Hébergeur : dépend de la plateforme qui héberge Keycloak, à vérifier avec elle.
| Capacité | Verdict | Précision |
|---|---|---|
| Realms, clients, utilisateurs, groupes, rôles de realm et de client, rôles composites | Natif | Socle du produit, administrable par console, API REST et kcadm.sh. |
| Ajout de rôles, groupes et attributs dans les jetons via client scopes et protocol mappers fournis | Natif | Les mappers OIDC et SAML fournis couvrent les besoins courants (attribut, rôle, groupe, audience, valeur fixe). |
| Claim calculé par une logique métier (appel à un référentiel, règle complexe) | Extension | Demande un ProtocolMapper écrit en Java et déployé comme provider. Le mapper JavaScript existe, mais il dépend d'une fonctionnalité à activer et doit lui aussi être packagé et déployé comme provider. |
| User profile déclaratif (attributs, validations, droits de lecture et d'écriture) | Natif | Activé par défaut et supporté depuis Keycloak 24, configuré en JSON par realm. |
| Validateur d'attribut spécifique (contrôle de clé, appel externe) | Extension | Les validateurs fournis (longueur, motif, e-mail, URI, liste de valeurs, date…) couvrent la plupart des cas ; au-delà, un Validator SPI. |
| Console de compte (profil, mots de passe, facteurs, sessions, comptes liés) | Natif | Pilotée par le user profile et les required actions. Une refonte fonctionnelle demande un thème ou une extension. |
| Accès au realm master et aux droits d'administration globaux | Hébergeur | Chez un hébergeur, l'accès au realm master et aux comptes d'administration est souvent restreint : à vérifier avec lui. |
Vérifier ce que vous avez retenu
5 questions
-
Une application métier a besoin de connaître le rôle « valideur » de l'utilisateur, qui n'a de sens que pour elle. Où le déclarer ?
Un rôle propre à une application est un rôle de client : il est rangé sous resource_access de ce client et ne pollue pas les autres. L'affecter via un groupe évite la gestion utilisateur par utilisateur.
-
Un client reçoit dans son access token tous les rôles de tous les clients du realm. Quelle est la cause la plus probable ?
Full scope allowed, actif par défaut, place dans le jeton tous les rôles de l'utilisateur. Le désactiver et déclarer les role scope mappings nécessaires limite le jeton à ce dont le client a besoin.
-
Vous voulez qu'un claim matricule n'apparaisse que pour les applications qui le demandent explicitement. Quel montage choisir ?
Un client scope optional n'est appliqué que si l'application l'inclut dans le paramètre scope. Le mapper est défini une fois et réutilisé.
-
Un usager ne doit jamais pouvoir modifier son numéro d'agent dans la console de compte, mais doit pouvoir le voir. Que configurer ?
Les permissions du user profile s'appliquent côté serveur, dans la console de compte comme dans les formulaires et l'API de compte. Un thème ne fait que masquer.
-
Un chef de projet vous demande d'ajouter « juste un attribut » dans le jeton. Quelle question posez-vous en premier ?
Le mapper est la partie facile. Il faut d'abord savoir d'où vient la donnée (annuaire, saisie de l'usager, application) et qui la modifie, ce qui se traduit dans le user profile et la fédération.