Le jeton d'identité OpenID Connect
Sur cette page
Quand une application se connecte avec OpenID Connect, TOSIAM lui remet un jeton d’identité (ID token) : un JWT signé qui dit qui s’est connecté, quand, comment et pour quelle application. Ce tutoriel l’ouvre revendication par revendication, le compare au point userinfo, puis montre comment l’application peut demander une méthode d’authentification avec acr_values et la retrouver dans les revendications acr et amr.
Le code est dans le dépôt tosiam-samples, dossier mini-angular : une petite application Angular 22, sans API, qui affiche le jeton reçu. Elle se connecte comme celle du tutoriel Application Angular avec OpenID Connect : code d’autorisation avec PKCE, client public.
Navigateur ── Angular (localhost:4201) ──① connexion (acr_values), code + PKCE──▶ TOSIAM (royaume ref)
◀──② jeton d'identité, jeton d'accès──
──③ userinfo avec le jeton d'accès──▶Étape 1 : le client de l’application#
Le fournisseur OAuth2 du royaume ref est celui du tutoriel Angular. Le client se crée de la même façon (étape 2 de ce tutoriel), avec ces valeurs :
| Réglage | Valeur |
|---|---|
| Identifiant | mini-angular-client |
| Type de client | Public, PKCE exigé, consentement implicite |
| URI de redirection et URI de redirection après déconnexion | http://localhost:4201 |
| Scopes | openid et profile (pas d’API à appeler, donc pas de scope d’API) |
| Algorithme de signature du jeton d’identité | RS256 |
L’algorithme se règle dans l’onglet Signature et chiffrement de la fiche du client. RS256 est asymétrique : l’application vérifie la signature avec les clés publiques publiées par TOSIAM (jwks_uri), sans connaître de secret.
RS256.Ajoutez une sous-configuration CORS pour l’origine http://localhost:4201, comme à l’étape 4 du tutoriel Angular : l’application appelle le point des jetons et le point userinfo depuis le navigateur.
Étape 2 : lire le jeton d’identité#
Après la connexion, l’application décode le jeton et affiche ses revendications. Voici un jeton réel délivré par TOSIAM :
Les revendications standard d’OpenID Connect :
| Revendication | Signification |
|---|---|
iss | Émetteur : http://localhost:8080/tosiam/oauth2/ref, l’adresse du fournisseur du royaume |
sub | Identifiant de l’utilisateur chez l’émetteur, stable : c’est lui que l’application doit garder pour reconnaître l’utilisateur |
aud | Audience : le client à qui le jeton est destiné, ici mini-angular-client |
azp | Partie autorisée : le client qui a demandé le jeton |
exp, iat | Expiration et émission, en secondes depuis 1970 (l’application les affiche aussi en date lisible) |
auth_time | Moment où l’utilisateur s’est authentifié (et non celui où le jeton a été émis) |
nonce | Valeur aléatoire envoyée par l’application dans la demande d’autorisation et recopiée dans le jeton : elle lie le jeton à cette demande |
at_hash, c_hash | Empreintes du jeton d’accès et du code d’autorisation délivrés avec ce jeton |
acr, amr | Classe et méthodes d’authentification (étape 4) |
Les revendications propres à TOSIAM : realm (le royaume), tokenName et tokenType (nature du jeton), auditTrackingId (pour retrouver la connexion dans les journaux d’audit) et org.forgerock.openidconnect.ops (référence de la session OpenID Connect que TOSIAM garde côté serveur).
L’en-tête du jeton donne l’algorithme (alg: RS256) et l’identifiant de la clé (kid). La clé publique correspondante est publiée à l’adresse jwks_uri du document de découverte (http://localhost:8080/tosiam/oauth2/ref/connect/jwk_uri).
Décoder n’est pas vérifier. Le décodage de l’application, dans frontend/src/app/id-token.ts, ne sert qu’à l’affichage :
export function decodePart(part: string): Record<string, unknown> {
const base64 = part.replace(/-/g, '+').replace(/_/g, '/');
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
const bytes = Uint8Array.from(atob(padded), c => c.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes));
}Avant d’accepter le jeton, il faut le vérifier. La bibliothèque angular-oauth2-oidc contrôle d’elle-même iss (égal à l’émetteur attendu), aud (contient l’identifiant du client), sub, iat et exp (le jeton n’est pas expiré) et nonce (égal à celui de la demande). Elle télécharge les clés de jwks_uri, mais ne vérifie pas la signature tant qu’on ne lui donne pas de validation handler : par défaut, un jeton à la signature fausse serait accepté.
L’exemple lui en donne un, écrit avec la bibliothèque jose :
const ALGORITHMS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'];
@Injectable()
export class JoseValidationHandler extends ValidationHandler {
async validateSignature(params: ValidationParams): Promise<unknown> {
try {
return await this.verify(params.idToken, params.jwks as JSONWebKeySet);
} catch (error) {
// Clé inconnue : TOSIAM a peut-être une nouvelle clé de signature, on recharge le jeu de clés
if ((error as { code?: string }).code === 'ERR_JWKS_NO_MATCHING_KEY') {
return this.verify(params.idToken, (await params.loadKeys()) as JSONWebKeySet);
}
throw error;
}
}
validateAtHash(): Promise<boolean> {
return Promise.resolve(true);
}
private verify(idToken: string, jwks: JSONWebKeySet) {
return jwtVerify(idToken, createLocalJWKSet(jwks), { algorithms: ALGORITHMS });
}
}provideOAuthClient(undefined, JoseValidationHandler),- La signature est vérifiée avec la clé
kiddu jeu de clés publié par TOSIAM, et seulement avec des algorithmes asymétriques : un client public n’a pas de secret pour vérifier une signature HMAC (HS256). - Un jeton altéré est refusé : l’application affiche « Échec de la connexion : signature verification failed ».
at_hashn’est pas contrôlé : dans le flux par code, les jetons arrivent directement du point des jetons, et la bibliothèque saute ce contrôle.
Étape 3 : jeton d’identité et point userinfo#
Le jeton ci-dessus ne contient ni le nom ni le prénom de l’utilisateur, alors que l’application a demandé le scope profile. C’est le comportement prévu par OpenID Connect quand un jeton d’accès est délivré : les informations du profil se lisent au point userinfo, avec ce jeton d’accès.
L’application appelle le point elle-même :
this.http.get<Record<string, unknown>>(this.oauthService.userinfoEndpoint!, {
headers: { Authorization: `Bearer ${this.oauthService.getAccessToken()}` },
}).subscribe({
next: info => this.userinfo.set(toClaims(info)),
error: error => this.message.set(`Échec de l'appel userinfo (erreur HTTP ${error.status})`),
});loadUserProfile() de la bibliothèque ferait aussi l’appel, mais fusionne la réponse avec les revendications du jeton d’identité : pour montrer d’où vient chaque valeur, l’exemple s’en passe.
Pour mettre le profil directement dans le jeton d’identité, activez Toujours renvoyer les revendications dans les jetons d’identité (alwaysAddClaimsToToken) dans le fournisseur OAuth2 du royaume. L’exemple ne le fait pas : ce réglage vaut pour tous les clients du royaume, et il grossit chaque jeton.
Étape 4 : demander une méthode d’authentification#
Avec le paramètre acr_values de la demande d’autorisation, l’application demande à TOSIAM une méthode d’authentification. Le fournisseur traduit chaque valeur en chaîne d’authentification par le réglage Correspondance acr_values OpenID Connect vers chaînes d’authentification (forgerock-oauth2-provider-loa-mapping), et indique dans le jeton ce qu’il a appliqué.
L’exemple ajoute deux valeurs au fournisseur, sans toucher aux réglages existants (--append) :
set-realm-svc-attrs --realm ref --servicename OAuth2Provider --append --attributevalues forgerock-oauth2-provider-loa-mapping=[mot-de-passe]=ldapService forgerock-oauth2-provider-amr-mappings=[pwd]=DataStore- Mappage acr
[mot-de-passe]=ldapService: la valeurmot-de-passedemande la chaîneldapService(identifiant et mot de passe). Le document de découverte la publie dansacr_values_supported. - Mappage amr
[pwd]=DataStore, réglage Correspondance des valeurs amr de l’id_token OpenID Connect vers les modules d’authentification : la clé est la valeuramr, la valeur est le module. Une connexion passée par le moduleDataStoredonneamr: ["pwd"]dans le jeton.
Dans la console, les deux réglages sont sur la page du fournisseur (Services › OAuth2 Provider), sous forme de paires clé / valeur :
mot-de-passe → ldapService (1) et mappage amr pwd → DataStore (2). Le badge « Modifié » signale une valeur différente de la valeur par défaut.Côté application, la valeur part avec la demande d’autorisation :
this.oauthService.initCodeFlow('', this.acrValues ? { acr_values: this.acrValues } : {});
Ce que montre le jeton selon la demande :
| Demande | Page de connexion | Revendication acr |
|---|---|---|
Sans acr_values | Méthode par défaut du royaume | Absente avec les réglages par défaut du fournisseur |
acr_values=mot-de-passe | Chaîne ldapService (service=ldapService dans l’adresse de la page de connexion) | mot-de-passe |
acr_values=inconnu (valeur non mappée) | Méthode par défaut du royaume | "0" |
acr_values=mot-de-passe, session TOSIAM déjà ouverte par une autre chaîne | Pas de page de connexion | "0" |
Pour proposer un niveau plus fort, créez une chaîne d’authentification supplémentaire (par exemple mot de passe puis code à usage unique) et ajoutez-lui une valeur au mappage acr, puis le module de second facteur au mappage amr.
Étape 5 : lancer et tester#
cd tosiam-samples/mini-angular/frontend
npm install
npx ng serve # http://localhost:4201Ouvrez http://localhost:4201, choisissez la méthode demandée, puis cliquez sur Login et connectez-vous, par exemple avec dduck / Donald-Duck-2026 si vous avez lancé up.sh. L’application affiche le jeton ; Lire userinfo affiche le profil ; Logout ferme la session TOSIAM.
Dans les outils de développement du navigateur, l’onglet Réseau montre les échanges :
| Requête | Ce qu’elle porte |
|---|---|
GET /tosiam/oauth2/ref/authorize | response_type=code, code_challenge, nonce, state, et acr_values si une méthode est choisie |
POST /tosiam/oauth2/ref/access_token | code, code_verifier ; la réponse contient id_token, access_token et refresh_token |
GET /tosiam/oauth2/ref/connect/jwk_uri | Les clés publiques qui vérifient la signature du jeton d’identité |
GET /tosiam/oauth2/ref/userinfo | Authorization: Bearer <jeton d'accès> ; la réponse contient sub, name, given_name, family_name |
En cas de problème#
| Symptôme | Cause probable |
|---|---|
ng serve refuse de démarrer (« Node.js version … detected ») | Version de Node.js trop ancienne pour Angular 22 : il faut 24.15 ou plus |
| Erreur CORS dans la console du navigateur | Origine http://localhost:4201 absente de la configuration CORS |
Page d’erreur invalid_client « Client authentication failed » au clic sur Login | Client mini-angular-client absent du royaume ref : refaites l’étape 1, ou relancez up.sh |
acr: "0" dans le jeton | Valeur d’acr_values absente du mappage acr du fournisseur, ou session TOSIAM déjà ouverte par une autre chaîne : déconnectez-vous puis recommencez |
| « Échec de la connexion : signature verification failed » | Signature du jeton d’identité invalide : jeton altéré, ou clé publique introuvable dans jwks_uri |
amr vide ou absente | Mappage amr absent, ou écrit à l’envers : la clé est la valeur amr, la valeur est le nom du module ([pwd]=DataStore) |
| « Échec de l’appel userinfo (erreur HTTP 401) » | Jeton d’accès expiré ou révoqué : reconnectez-vous |
En production#
- Le jeton d’identité est pour l’application, pas pour une API : son audience est le client. Une API reçoit le jeton d’accès (voir le tutoriel Angular, étape 6).
- Vérifiez
acrcôté serveur quand une méthode d’authentification conditionne l’accès : un contrôle dans le navigateur se contourne. - Ne journalisez pas les jetons : le jeton d’identité contient des données personnelles, et les revendications ajoutées par
alwaysAddClaimsToTokenen ajoutent. - HTTPS partout, comme pour toute application OpenID Connect.
Pour aller plus loin#
- Service à service avec client_credentials : des jetons d’accès JWT pour un service sans utilisateur, validés par l’API avec les clés publiques de TOSIAM.
- OpenID Connect et JWT : les principes.
- Application Angular avec OpenID Connect : la configuration du fournisseur et des clients dans la console, et une API protégée.
- Fournisseur OAuth2 : les autres réglages du fournisseur, dont la signature des jetons.
Mis à jour le