TutorielGuides

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.

texte
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 :

Faites défiler le tableau
RéglageValeur
Identifiantmini-angular-client
Type de clientPublic, PKCE exigé, consentement implicite
URI de redirection et URI de redirection après déconnexionhttp://localhost:4201
Scopesopenid 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.

Fiche du client mini-angular-client, onglet Signature et chiffrement : algorithme de signature du jeton d'identité RS256, chiffrement du jeton désactivé Fiche du client mini-angular-client, onglet Signature et chiffrement : algorithme de signature du jeton d'identité RS256, chiffrement du jeton désactivé
Algorithme de signature du jeton d’identité : 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 :

Tableau des revendications du jeton d'identité : at_hash, sub, auditTrackingId, amr, iss, tokenName, nonce, aud, c_hash, acr, org.forgerock.openidconnect.ops, azp, auth_time, realm, exp, tokenType, iat Tableau des revendications du jeton d'identité : at_hash, sub, auditTrackingId, amr, iss, tokenName, nonce, aud, c_hash, acr, org.forgerock.openidconnect.ops, azp, auth_time, realm, exp, tokenType, iat
Le jeton d’identité, signé en RS256, revendication par revendication.

Les revendications standard d’OpenID Connect :

Faites défiler le tableau
RevendicationSignification
issÉmetteur : http://localhost:8080/tosiam/oauth2/ref, l’adresse du fournisseur du royaume
subIdentifiant de l’utilisateur chez l’émetteur, stable : c’est lui que l’application doit garder pour reconnaître l’utilisateur
audAudience : le client à qui le jeton est destiné, ici mini-angular-client
azpPartie autorisée : le client qui a demandé le jeton
exp, iatExpiration et émission, en secondes depuis 1970 (l’application les affiche aussi en date lisible)
auth_timeMoment où l’utilisateur s’est authentifié (et non celui où le jeton a été émis)
nonceValeur 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_hashEmpreintes du jeton d’accès et du code d’autorisation délivrés avec ce jeton
acr, amrClasse 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 :

typescript· frontend/src/app/id-token.ts
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 :

typescript· frontend/src/app/jose-validation-handler.ts
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 });
  }
}
typescript· frontend/src/app/app.config.ts
provideOAuthClient(undefined, JoseValidationHandler),
  • La signature est vérifiée avec la clé kid du 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_hash n’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.

Tableau du point userinfo : sub, name, given_name, family_name Tableau du point userinfo : sub, name, given_name, family_name
Le profil lu au point userinfo avec le jeton d’accès.

L’application appelle le point elle-même :

typescript· frontend/src/app/app.component.ts
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.

Réglages du fournisseur OAuth2 du royaume ref : Toujours renvoyer les revendications dans les jetons d'identité, désactivé Réglages du fournisseur OAuth2 du royaume ref : Toujours renvoyer les revendications dans les jetons d'identité, désactivé
Le réglage du fournisseur, désactivé par défaut (Services › OAuth2 Provider).

É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) :

texte· mini-angular/tosiam/ssoadm.cfg
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 valeur mot-de-passe demande la chaîne ldapService (identifiant et mot de passe). Le document de découverte la publie dans acr_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 valeur amr, la valeur est le module. Une connexion passée par le module DataStore donne amr: ["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 :

Réglages du fournisseur OAuth2 du royaume ref : mappage acr mot-de-passe vers ldapService, mappage amr pwd vers DataStore Réglages du fournisseur OAuth2 du royaume ref : mappage acr mot-de-passe vers ldapService, mappage amr pwd vers DataStore
Mappage acr 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 :

typescript· frontend/src/app/app.component.ts
this.oauthService.initCodeFlow('', this.acrValues ? { acr_values: this.acrValues } : {});
Page d'accueil de l'application : liste Méthode demandée (acr_values) sur mot-de-passe, bouton Login Page d'accueil de l'application : liste Méthode demandée (acr_values) sur mot-de-passe, bouton Login
La méthode demandée part dans le paramètre acr_values.

Ce que montre le jeton selon la demande :

Faites défiler le tableau
DemandePage de connexionRevendication acr
Sans acr_valuesMéthode par défaut du royaumeAbsente avec les réglages par défaut du fournisseur
acr_values=mot-de-passeChaî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înePas 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#

bash
cd tosiam-samples/mini-angular/frontend
npm install
npx ng serve            # http://localhost:4201

Ouvrez 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 :

Faites défiler le tableau
RequêteCe qu’elle porte
GET /tosiam/oauth2/ref/authorizeresponse_type=code, code_challenge, nonce, state, et acr_values si une méthode est choisie
POST /tosiam/oauth2/ref/access_tokencode, code_verifier ; la réponse contient id_token, access_token et refresh_token
GET /tosiam/oauth2/ref/connect/jwk_uriLes clés publiques qui vérifient la signature du jeton d’identité
GET /tosiam/oauth2/ref/userinfoAuthorization: Bearer <jeton d'accès> ; la réponse contient sub, name, given_name, family_name

En cas de problème#

Faites défiler le tableau
SymptômeCause 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 navigateurOrigine http://localhost:4201 absente de la configuration CORS
Page d’erreur invalid_client « Client authentication failed » au clic sur LoginClient mini-angular-client absent du royaume ref : refaites l’étape 1, ou relancez up.sh
acr: "0" dans le jetonValeur 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 absenteMappage 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 acr cô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 alwaysAddClaimsToToken en ajoutent.
  • HTTPS partout, comme pour toute application OpenID Connect.

Pour aller plus loin#

Mis à jour le