> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Découvrez des exemples de cas d’utilisation de Custom Token Exchange, accompagnés d’exemples de code pour la mise en œuvre.

# Exemples de cas d’utilisation

export const ReleaseStageNotice = ({feature, stage, plans, contact, terms}) => {
  const stageTextMap = {
    "beta": "bêta",
    "ea": "Accès anticipé"
  };
  const stageText = stageTextMap[stage] || "une phase de lancement du produit";
  const prsLink = "/docs/troubleshoot/product-lifecycle/product-release-stages";
  const linkify = (text, url) => {
    return <a href={url} target="_blank" rel="noreferrer" class="link">{text}</a>;
  };
  const includeDetails = (plans, contact, terms) => {
    const hasDetails = terms || plans || contact;
    if (!hasDetails) return null;
    return <span data-as="p">
            {plans && <>Cette fonctionnalité est offerte avec les forfaits {linkify(`${plans}`, "https://auth0.com/pricing")}. </>}
            {contact && "Pour y participer, communiquez avec " + contact + ". "}
            {terms && <>En utilisant cette fonctionnalité, vous acceptez les conditions applicables de l’essai gratuit énoncées dans le {linkify("Master Subscription Agreement", "https://www.okta.com/legal")} d’Okta.</>}
        </span>;
  };
  return <Warning>
            <span data-as="p">
                <strong>La fonctionnalité {feature} est en {linkify(stageText, prsLink)}.</strong>
            </span>

            {includeDetails(plans, contact, terms)}
        </Warning>;
};

<ReleaseStageNotice feature="Échange de jeton personnalisé (CTE)" stage="ea" plans="B2C Professional, B2B Professional, and Enterprise" terms="true" />

Vous pouvez utiliser l’Échange de jeton personnalisé pour résoudre des scénarios d’intégration avancés où les stratégies habituelles de connexion fédérée reposant sur la redirection de l’utilisateur final ne peuvent pas être appliquées en raison de contraintes techniques ou liées à l’expérience utilisateur. Le code fourni pour les cas d’utilisation est incomplet et vise uniquement à illustrer les étapes logiques que vous pouvez suivre dans votre propre code pour répondre au cas d’utilisation. Consultez les [exemples de code](#code-samples) pour obtenir des exemples plus détaillés.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Auth0 recommande d’utiliser, dans la mesure du possible, la connexion fédérée standard offerte par défaut. En vous permettant de définir l’utilisateur pour la transaction, l’Échange de jeton personnalisé vous offre plus de flexibilité, mais vous confie également la responsabilité supplémentaire de valider et de traiter la transaction de façon sécuritaire.
</Callout>

<div id="use-cases">
  ## Cas d’utilisation
</div>

Cette section présente des exemples de cas d’utilisation ainsi que des exemples de code précis, accompagnés de recommandations pour mettre en œuvre votre scénario. Pour illustrer les cas d’utilisation, nous utiliserons GearUp, une entreprise fictive de location de voitures.

<div id="use-case-seamless-migration-into-auth0">
  ### Cas d’utilisation : Migration transparente vers Auth0
</div>

GearUp dispose d’une application mobile utilisée par des millions de personnes et doit moderniser sa solution d’identité; l’entreprise a donc décidé de passer à Auth0. Cependant, elle veut éviter de forcer les utilisateurs à s’authentifier de nouveau pendant la migration à partir de son IdP hérité, car cela ajoute de la friction à l’expérience utilisateur.

Pour résoudre ce problème et limiter les risques, GearUp procède par migration progressive. Pour chaque utilisateur, l’entreprise souhaite échanger le jeton d’actualisation de son IdP hérité contre un ensemble composé d’un Auth0 jeton d’accès, d’un jeton d’actualisation et d’un <Tooltip tip="ID Token : justificatif destiné au client lui-même plutôt qu’à l’accès à une ressource." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=ID+token">ID token</Tooltip>. Cela permet à son application de commencer, en toute transparence, à utiliser Auth0 comme IdP pour cet utilisateur, ainsi qu’à consommer les API de GearUp au moyen de jetons émis par Auth0. Une fois l’échange effectué pour tous les utilisateurs, l’application sera entièrement migrée et l’ancien IdP pourra être déconnecté, le tout sans incidence sur les utilisateurs finaux ni sur les activités de GearUp.

<Frame>
  <img src="https://mintcdn.com/translations/Dcx0M11uuptU53TX/docs/images/cdy7uua7fh8z/2Ke6p3yZl06KT4HHqtaVu9/5d9c5feb98d614d6d793fb01ccc03e92/Screenshot_2025-02-03_at_5.00.32_PM.png?fit=max&auto=format&n=Dcx0M11uuptU53TX&q=85&s=fcfd47a263844372a912c51564f65e33" alt="" width="1222" height="720" data-path="docs/images/cdy7uua7fh8z/2Ke6p3yZl06KT4HHqtaVu9/5d9c5feb98d614d6d793fb01ccc03e92/Screenshot_2025-02-03_at_5.00.32_PM.png" />
</Frame>

Comme préalable, GearUp a effectué une [importation en bloc d’utilisateurs](/docs/fr-ca/manage-users/user-migration/bulk-user-imports) dans son tenant Auth0, et l’application mobile possède un jeton d’actualisation hérité valide pour chaque utilisateur à migrer.

1. L’application mobile envoie une request à Auth0 pour échanger le jeton d’actualisation hérité, en le définissant comme subject token.
2. L’Action du profil Échange de jeton personnalisé correspondant s’exécute. Elle valide le jeton d’actualisation auprès de l’IdP hérité et obtient l’ID utilisateur externe à partir du profil utilisateur. Elle applique ensuite la politique d’autorisation requise, puis définit l’utilisateur.
3. Auth0 répond avec un Auth0 jeton d’accès, un ID token et un jeton d’actualisation.
4. L’application mobile peut maintenant utiliser les Customer APIs au moyen de jetons Auth0, sans que l’utilisateur ait à s’authentifier de nouveau.

L’exemple de code suivant montre comment implémenter cela dans l’Action Échange de jeton personnalisé. Dans ce cas-ci, puisque les profils utilisateur ont déjà été importés dans une database connection Auth0 :

* Nous ne voulons pas créer l’utilisateur.
* Nous ne voulons pas mettre à jour le profil utilisateur.

Nous utilisons l’ID utilisateur de l’IdP externe pour définir l’utilisateur dans la connection correspondante.

```javascript lines expandable theme={null}
/**
* Gestionnaire à exécuter lors d'une demande d'échange de jeton personnalisé
* @param {Event} event - Détails sur la demande d'échange de jeton entrante.
* @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
*/
exports.onExecuteCustomTokenExchange = async (event, api) => {

 // 1. VALIDER le refresh_token reçu dans le subject_token en l'utilisant pour obtenir
 // le profil utilisateur depuis l'IdP externe
 const { isValid, user } = await getUserProfile(
   event.transaction.subject_token,
   event.secrets.CLIENT_SECRET,
 );

 if (!isValid) {
   // Marquer le subject token comme invalide et faire échouer la transaction.
   api.access.rejectInvalidSubjectToken("Invalid subject_token");
 } else {
   // 2. Appliquer votre POLITIQUE D'AUTORISATION selon les besoins pour déterminer si la demande est valide.
   // Utiliser api.access.deny() pour rejeter la transaction dans ces cas.

   // 3. Une fois le profil obtenu, nous DÉFINISSONS L'UTILISATEUR dans la connexion cible
   api.authentication.setUserByConnection(
     connectionName,
     {
       // seul le user_id dans la connexion est nécessaire, car nous ne
       // créons ni ne mettons à jour l'utilisateur
       user_id: user.sub,
     },
     {
       creationBehavior: "none",
       updateBehavior: "none",
     },
   );
 }
};

/**
* Échanger le jeton d’actualisation et charger le profil utilisateur depuis l'IdP hérité
* @param {string} refreshToken
* @param {string} clientSecret
* @returns {Promise<{ isValid: boolean, user?: object }>} Si le jeton d’actualisation a été échangé avec succès, retourne le profil utilisateur
*/
async function getUserProfile(refreshToken, clientSecret) {
 // Ajoutez votre code ici. CONSULTEZ LES EXEMPLES DE CODE POUR DES EXEMPLES DÉTAILLÉS
}
```

<div id="use-case-re-use-an-external-authentication-provider">
  ### Cas d’utilisation : Réutiliser un fournisseur d’authentification externe
</div>

Un autre cas d’utilisation met en scène GearUp, qui s’associe à Air0, un important fournisseur de services de voyage, afin d’offrir ses services de location de voitures directement dans l’application monopage d’Air0. GearUp propose une bibliothèque JavaScript qui encapsule l’utilisation de ses API. Ainsi, les API de GearUp peuvent être facilement utilisées par le site Web d’Air0, où les services de location de voitures sont offerts.

Encore une fois, la solution doit être invisible pour les utilisateurs finaux en évitant qu’ils aient à se réauthentifier auprès de GearUp. Pour résoudre ce problème, la bibliothèque JavaScript de GearUp peut effectuer un échange de jeton en utilisant le ID token externe d’Air0 comme entrée. Cela produit un jeton d’accès Auth0 généré et associé à l’utilisateur GearUp correspondant en fonction de son adresse courriel. Une fois le jeton d’accès obtenu par la bibliothèque GearUp, celle-ci peut commencer à utiliser les API de GearUp pour offrir des services de location de voitures directement sur le site Web d’Air0.

<Frame>
  <img src="https://mintcdn.com/translations/Dcx0M11uuptU53TX/docs/images/cdy7uua7fh8z/34AVzwyYARK6fn2IEnLsQn/409082d736d8495b637626406977fb1f/Screenshot_2025-02-03_at_5.08.47_PM.png?fit=max&auto=format&n=Dcx0M11uuptU53TX&q=85&s=50d9d43dc3193f6f3194bcdfb14167c3" alt="" width="1260" height="730" data-path="docs/images/cdy7uua7fh8z/34AVzwyYARK6fn2IEnLsQn/409082d736d8495b637626406977fb1f/Screenshot_2025-02-03_at_5.08.47_PM.png" />
</Frame>

Comme condition préalable, GearUp a configuré l’IdP Air0 comme une connexion d’entreprise fédérée ou une connexion sociale, afin que l’utilisateur puisse s’authentifier au moyen d’une connexion fédérée ou, autrement, au moyen de l’Échange de jeton personnalisé, comme suit :

1. L’application monopage obtient le ID token de l’IdP externe une fois que l’utilisateur s’authentifie.
2. Elle demande ensuite l’échange du ID token en le définissant comme subject token.
3. L’Action du profil d’Échange de jeton personnalisé correspondant s’exécute. Elle valide le ID token et récupère le user ID ainsi que d’autres attributs du profil à partir du jeton. Elle applique ensuite la politique d’autorisation requise, puis définit l’utilisateur.
4. Auth0 répond avec un jeton d’accès Auth0, un ID token et un jeton d’actualisation.
5. Le code JavaScript exécuté dans la SPA peut maintenant utiliser les Customer APIs avec des jetons Auth0, sans que l’utilisateur ait à se réauthentifier.

Le code suivant montre comment implémenter cela dans l’Action d’Échange de jeton personnalisé. Dans ce cas-ci :

* Nous utilisons le user ID de l’IdP externe pour définir l’utilisateur dans la connexion correspondante.
* Nous voulons créer l’utilisateur s’il n’existe pas encore.
* Nous ne voulons pas remplacer le profil utilisateur si un ensemble d’attributs plus complet est obtenu au moyen d’une connexion fédérée, dans le cas où l’utilisateur existe déjà.
* Nous ne voulons pas vérifier les courriels lorsque des utilisateurs sont créés.

```javascript lines expandable theme={null}
const jwksUri = "https://example.com/.well-known/jwks.json";

/**
 * Gestionnaire à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {

  // 1. VALIDER le id_token reçu dans le subject_token
  const { isValid, payload } = await validateToken(
    event.transaction.subject_token,
  );

  if (!isValid) {
    // Marquer le subject token comme invalide et faire échouer la transaction.
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
  } else {
    // 2. Appliquer votre POLITIQUE D'AUTORISATION au besoin pour déterminer si la demande est valide.
    // Utiliser api.access.deny() pour rejeter la transaction dans ces cas.

    // 3. DÉFINIR L'UTILISATEUR dans la connexion cible.
    // Nous ne voulons pas vérifier les courriels lors de la création des utilisateurs
    // Cet exemple suppose que le subject_token (id_token) contient des revendications OIDC standard. D'autres mappages personnalisés
    // sont également possibles.
    api.authentication.setUserByConnection(
      'Enterprise-OIDC',
      {
          user_id: formattedUserId,
          email: subject_token.email,
          email_verified: subject_token.email_verified,
          phone_number: subject_token.phone_number,
          phone_verified: subject_token.phone_number_verified,
          username: subject_token.preferred_username,
          name: subject_token.name,
          given_name: subject_token.given_name,
          family_name: subject_token.family_name,
          nickname: subject_token.nickname,
          verify_email: false
      },
      {
          creationBehavior: 'create_if_not_exists',
          updateBehavior: 'none'
      }
    );
  }

  /**
   * Valider le subject token
   * @param {string} subjectToken
   * @returns {Promise<{ isValid: boolean, payload?: object }>} Contenu du jeton
   */
  async function validateToken(subjectToken) {
    // Ajoutez votre code ici. CONSULTEZ LES EXEMPLES DE CODE POUR DES EXEMPLES DÉTAILLÉS
  }
};
```

Consultez les [exemples de code](#code-samples) pour un exemple plus détaillé montrant comment valider de façon sécurisée des <Tooltip tip="JSON Web Token (JWT) : format standard des ID Token (et souvent des access token), utilisé pour représenter des claims de façon sécurisée entre deux parties." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=JWTs">JWTs</Tooltip>.

<div id="use-case-get-auth0-tokens-for-another-audience">
  ### Cas d’utilisation : Obtenir des jetons Auth0 pour une autre audience
</div>

GearUp veut améliorer la façon dont il autorise les appels entre ses microservices internes pour traiter les requêtes d’API. Il souhaite mettre en place une politique centralisée qui contrôle les ressources que chaque service peut consommer. Ce scénario peut aussi être résolu à l’aide de l’échange de jetons.

Lorsqu’une requête d’API arrive d’abord au service A, celui-ci échange le jeton d’accès reçu contre un nouveau jeton qui lui permet de consommer le service B avec une nouvelle audience. Si la politique d’autorisation qui régit l’échange de jetons l’autorise, le service A récupère le nouveau jeton et peut alors consommer le service B. L’ID utilisateur reste inchangé dans le nouveau jeton, ce qui permet de conserver le bon contexte utilisateur tout au long du processus.

<Frame>
  <img src="https://mintcdn.com/translations/MV7tE-x71x8RWRES/docs/images/cdy7uua7fh8z/5Zw7yaJGct9eHAl4rdf72D/42274a5896851a16bea402ac52037f52/Screenshot_2025-02-03_at_5.17.14_PM.png?fit=max&auto=format&n=MV7tE-x71x8RWRES&q=85&s=b7f53192c4c37861e3dd87fd86992f77" alt="" width="1240" height="694" data-path="docs/images/cdy7uua7fh8z/5Zw7yaJGct9eHAl4rdf72D/42274a5896851a16bea402ac52037f52/Screenshot_2025-02-03_at_5.17.14_PM.png" />
</Frame>

L’application GearUp a d’abord obtenu un jeton d’accès pour consommer l’API A au nom d’un utilisateur :

1. L’application envoie la requête à l’API A avec le jeton d’accès initial.
2. Le service backend de l’API A valide le jeton d’accès et demande un échange en le définissant comme subject token pour obtenir un nouveau jeton d’accès permettant de consommer l’API B.
3. L’Action correspondante du profil Échange de jeton personnalisé s’exécute. Elle valide le jeton d’accès et récupère l’ID utilisateur Auth0 à partir du jeton. Elle applique ensuite la politique d’autorisation requise, puis définit l’utilisateur.
4. Auth0 renvoie un jeton d’accès Auth0 permettant de consommer l’audience de l’API B.
5. Le service backend de l’API A appelle l’API B à l’aide du nouveau jeton d’accès, qui est toujours associé au même utilisateur.

Le code suivant montre comment mettre cela en œuvre dans l’Action Échange de jeton personnalisé. Dans ce cas-ci :

* Nous utilisons l’ID utilisateur Auth0 pour définir l’utilisateur; il n’est donc pas nécessaire de le définir dans la portée d’une quelconque connection.
* Nous ne voulons pas créer ni mettre à jour l’utilisateur.

Consultez [Validate JWTs signed with asymmetric keys](#validate-jwts-signed-with-asymmetric-keys) pour voir des exemples de code plus détaillés pour ce cas d’utilisation.

```javascript lines expandable theme={null}
const jwksUri = "https://example.com/.well-known/jwks.json";

/**
 * Gestionnaire à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {
  // 1. VALIDER l'access_token reçu dans le subject_token
  const { isValid, payload } = await validateToken(
    event.transaction.subject_token,
  );

  if (!isValid) {
    // Marquer le subject token comme invalide et faire échouer la transaction.
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
  } else {
    // 2. Appliquer votre POLITIQUE D'AUTORISATION au besoin pour déterminer si la demande est valide.
    // Utiliser api.access.deny() pour rejeter la transaction dans ces cas.

    // 3. DÉFINIR L'UTILISATEUR
    api.authentication.setUserById(payload.sub);
  }

  /**
   * Valider le subject token
   * @param {string} subjectToken
   * @returns {Promise<{ isValid: boolean, payload?: object }>} Contenu du jeton
   */
  async function validateToken(subjectToken) {
    // Ajoutez votre code ici. CONSULTEZ LES EXEMPLES DE CODE POUR DES EXEMPLES DÉTAILLÉS
  }
};
```

Consultez les [exemples de code](#code-samples) pour voir un exemple plus détaillé de validation sécurisée des JWTs.

<div id="use-case-perform-mfa-during-custom-token-exchange">
  ### Cas d’utilisation : Effectuer une vérification MFA pendant l’échange de jeton personnalisé
</div>

En s’appuyant sur le [cas d’utilisation : Réutiliser un fournisseur d’authentification externe](#use-case%3A-re-use-an-external-authentication-provider), GearUp souhaite maintenant confirmer la présence de l’utilisateur lorsqu’un jeton provenant du fournisseur d’authentification externe est utilisé. Cela est nécessaire pour atténuer les risques de sécurité, comme le vol de jeton ou les situations où la MFA n’est pas prise en charge par l’authentificateur externe. GearUp dispose de deux options pour y parvenir : mettre en œuvre une politique MFA à l’échelle de l’organisation ou déclencher la MFA par programmation à l’aide d’une Action Post Login.

L’exemple suivant utilise une Action PostLogin pour déclencher l’authentification MFA pendant la transaction d’échange de jeton personnalisé. Pour en savoir plus sur l’utilisation de l’octroi MFA avec des API intégrées, consultez la [documentation](/docs/fr-ca/secure/multi-factor-authentication/authenticate-using-ropg-flow-with-mfa) sur l’utilisation de ROPG avec la MFA, puisque l’échange de jeton personnalisé suit le même modèle.

Commencez par définir l’Action en utilisant `api.multifactor.enable()` pour déclencher une demande de vérification MFA.  Cette fonction est décrite dans la [documentation de l’API Post Login](/docs/fr-ca/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger/post-login-api-object).

```js lines theme={null}
exports.onExecutePostLogin = async (event, api) => {
  api.multifactor.enable('any');
};

Nous pouvons maintenant envoyer une demande d'échange de jetons :

curl --location 'https://{yourDomain}/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'audience=https://api.gearup.com' \
--data-urlencode 'scopes=openid offline_access gearup-scope1 gearup-scope2' \
--data-urlencode 'subject_token_type=urn:gearup:external-idp' \
--data-urlencode 'subject_token=t8e7S2D9trQm73e .... iqBR3GjxDtbDVjpfQU' \
--data-urlencode 'client_id=<YOUR_CLIENT_ID>' \
--data-urlencode 'client_secret=<YOUR_CLIENT_SECRET>'
```

Cela provoque une erreur `mfa_required`, qui renvoie un jeton MFA :

```json lines theme={null}
403 Forbidden
{
  "error": "mfa_required",
  "error_description": "Multifactor authentication required",
  "mfa_token": "<YOUR_MFA_TOKEN>"
}
```

Avec le `mfa_token` renvoyé, l’application peut ensuite appeler l’API MFA pour lancer une demande de vérification et vérifier un facteur :

D’abord, récupérez la liste des authentificateurs :

```bash lines theme={null}
curl --location 'https://{yourDomain}.auth0.com/mfa/authenticators' \
--header 'Authorization: Bearer <YOUR_MFA_TOKEN>' \


[
    {
        "id": "sms|dev_1MHoE3huPRB5dcDJ",
        "authenticator_type": "oob",
        "active": true,
        "oob_channel": "sms",
        "name": "XXXXXXXX6220"
    },
    {
        "id": "email|dev_QLGL8cGsvFFnOloK",
        "authenticator_type": "oob",
        "active": true,
        "oob_channel": "email",
        "name": "dloz********@gmai*****"
    }
]
```

Ensuite, utilisez l’ID de l’authentificateur pour lancer une demande de vérification :

```bash lines theme={null}
curl --location 'https://{yourDomain}.auth0.com/mfa/challenge' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<YOUR_CLIENT_ID>' \
--data-urlencode 'client_secret=<YOUR_CLIENT_SECRET>'
--data-urlencode 'mfa_token=<YOUR_MFA_TOKEN>' \
--data-urlencode 'authenticator_id=sms|dev_1MHoE3huPRB5dcDJ' \
--data-urlencode 'challenge_type=oob'
```

La demande d’authentification multifacteur génère la réponse suivante :

```json lines theme={null}
{
  "challenge_type": "oob",
  "oob_code": "<YOUR_OOB_CODE>",
  "binding_method": "prompt"
}
```

Utilisez `mfa_token` et `oob_code` (s’ils sont renvoyés) pour finaliser le processus de vérification à l’aide du point de terminaison de jeton et obtenir des jetons :

```bash lines theme={null}
curl --location 'https://{yourDomain}.auth0.com/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=http://auth0.com/oauth/grant-type/mfa-oob' \
--data-urlencode 'mfa_token=<YOUR_MFA_TOKEN>' \
--data-urlencode 'oob_code=<YOUR_OOB_CODE>' \
--data-urlencode 'binding_code=<YOUR_USER_CODE>'
```

Consultez les [exemples de code](#code-samples) pour obtenir un exemple plus détaillé de la validation d’un jeton d’actualisation opaque avec l’IdP hérité.

<div id="use-case-support-agent-acting-on-behalf-of-an-end-user-via-api">
  ### Cas d’utilisation : agent de soutien agissant au nom d’un utilisateur final par API
</div>

Les agents de soutien de GearUp doivent accéder aux données des utilisateurs finaux et effectuer des actions au moyen des API backend de GearUp au nom de l’utilisateur final. L’outil de soutien authentifie l’agent, puis utilise l’Échange de jeton personnalisé pour obtenir un jeton d’accès représentant l’utilisateur final, l’agent étant suivi comme acteur.

<Frame>
  <img src="https://mintcdn.com/translations/xwVvTWJUElMm5YAK/docs/images/custom-token-exchange/Support-agent-acting-on-behalf-of-an-end-user.png?fit=max&auto=format&n=xwVvTWJUElMm5YAK&q=85&s=b1f5fe252a592b5f9015b379b62e7439" alt="" width="1536" height="858" data-path="docs/images/custom-token-exchange/Support-agent-acting-on-behalf-of-an-end-user.png" />
</Frame>

Dans ce cas, l’ID token Auth0 de l’agent est envoyé comme `actor_token` dans la requête, et un JWT signé identifiant l’utilisateur final est envoyé comme `subject_token`. Lorsque `actor_token_type` est défini sur `urn:ietf:params:oauth:token-type:id_token`, Auth0 valide automatiquement le token (signature, expiration, issuer) et remplit `event.transaction.actor_token_user` avec le profil de l’agent. Cela élimine le besoin d’écrire du code de validation personnalisé pour l’actor token.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  L’utilisation d’un ID token Auth0 comme `actor_token` n’est pas obligatoire. Lorsque `actor_token_type` correspond à une valeur personnalisée, l’Action doit valider l’actor token au moyen de code personnalisé, comme pour la validation des subject tokens. Le remplissage automatique de `event.transaction.actor_token_user` s’applique uniquement aux ID tokens Auth0.
</Callout>

1. L’outil de soutien authentifie l’agent avec Auth0 et obtient l’ID token de l’agent.
2. L’outil de soutien envoie une requête à `/oauth/token` d’Auth0 au moyen d’une [requête d’Échange de jeton personnalisé](/docs/fr-ca/get-started/authentication-and-authorization-flow/token-exchange-flow/call-your-api-using-the-custom-token-exchange-flow), incluant un JWT signé avec l’identifiant de l’utilisateur final comme `subject_token` et l’ID token de l’agent comme `actor_token`.
3. L’[Action Échange de jeton personnalisé](/docs/fr-ca/customize/actions/explore-triggers/signup-and-login-triggers/custom-token-exchange-trigger) valide le subject token, vérifie que l’acteur a le droit d’agir au nom de l’utilisateur final et appelle `api.authentication.setActor()`.
4. Auth0 émet des jetons avec le claim `act` identifiant l’agent de soutien.
5. L’agent de soutien utilise l’API au nom de l’utilisateur final. Les API peuvent inspecter le claim `act` pour appliquer des politiques d’autorisation propres à l’accès délégué, par exemple en restreignant les opérations d’écriture ou en consignant l’activité à des fins d’audit.

L’Action Échange de jeton personnalisé détermine ce qu’il faut inclure dans l’objet actor, y compris les propriétés personnalisées et les niveaux d’imbrication. Consultez la [documentation de l’objet API Échange de jeton personnalisé](/docs/fr-ca/customize/actions/explore-triggers/signup-and-login-triggers/custom-token-exchange-trigger/custom-token-exchange-api-object) pour connaître les contraintes.

```javascript lines expandable theme={null}
const jwksUri = "https://gearup.com/.well-known/jwks.json";

/**
 * Gestionnaire à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {

  // 1. VALIDER le jeton de l'utilisateur final reçu dans le subject_token
  const { isValid, payload } = await validateToken(event.transaction.subject_token);

  if (!isValid) {
    api.access.rejectInvalidSubjectToken("Jeton subject_token invalide");
    return;
  }

  // 2. AUTORISER l'acteur — vérifier que l'agent a le droit d'agir au nom de cet utilisateur final
  const actorUser = event.transaction.actor_token_user;
  if (!actorUser) {
    api.access.deny("invalid_request", "Un jeton d'acteur est requis pour ce profil");
    return;
  }

  const isAuthorized = await checkDelegationPolicy(actorUser.user_id, payload.sub);
  if (!isAuthorized) {
    api.access.deny("unauthorized_actor", "L'agent n'est pas autorisé à agir au nom de cet utilisateur");
    return;
  }

  // 3. DÉFINIR L'ACTEUR pour inclure la revendication act dans les jetons émis
  api.authentication.setActor({
    sub: actorUser.user_id,
    sub_profile: "human",
    role: "support"
  });

  // 4. DÉFINIR L'UTILISATEUR pour la transaction (l'utilisateur final visé par l'action)
  api.authentication.setUserById(payload.sub);

  async function validateToken(subjectToken) {
    // Ajoutez votre code ici. CONSULTEZ LES EXEMPLES DE CODE POUR DES EXEMPLES DÉTAILLÉS
  }

  async function checkDelegationPolicy(agentId, userId) {
    // Implémentez votre vérification de politique de délégation ici.
    // Par exemple, vérifiez que l'agent fait partie de l'équipe de soutien
    // et qu'il est attribué à la région de cet utilisateur.
    return true;
  }
};
```

Le jeton d’accès délivré comprendra la claim `act` :

```json lines theme={null}
{
  "sub": "auth0|end_user_id",
  "aud": "https://api.gearup.com",
  "act": {
    "sub": "auth0|support_agent_id",
    "sub_profile": "human",
    "role": "support"
  }
}
```

<div id="important-considerations-for-delegated-authorization">
  #### Considérations importantes relatives à l’autorisation déléguée
</div>

Lorsque vous mettez en œuvre l’autorisation déléguée avec Échange de jeton personnalisé, suivez les lignes directrices suivantes :

* **Mettez en œuvre la logique d’autorisation dans votre Action d’échange de jeton personnalisé** afin de vérifier que l’acteur est autorisé à accéder au compte d’utilisateur visé. Par exemple, vous pouvez faire en sorte que seuls certains acteurs soient autorisés à obtenir un accès délégué, ou vérifier que l’utilisateur cible a un ticket de soutien actif afin d’éviter tout accès arbitraire à un compte d’utilisateur.

* **Validez les scopes demandés** afin de vous assurer que seul l’ensemble minimal de scopes requis pour l’autorisation déléguée est émis. Vous pouvez aussi vous assurer que certaines opérations sensibles ne puissent jamais être effectuées dans le contexte d’une autorisation déléguée.

* **Assurez-vous que vos API utilisent le contexte de délégation** dans la claim `act` du jeton d’accès. Vous devriez conserver dans vos API des journaux d’audit des actions effectuées par un acteur délégué et vous assurer de pouvoir clairement déterminer quel acteur a effectué des opérations au nom de l’utilisateur.

* **À des fins d’audit**, vous pouvez utiliser les détails de l’acteur dans les journaux du tenant Auth0. Les transactions Échange de jeton personnalisé réussies (événements de journal `secte`) incluent la propriété `actor` avec `sub` et toute information `actor` imbriquée.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Auth0 n’informe pas l’utilisateur final lorsqu’un jeton d’autorisation déléguée est émis en son nom. Si votre cas d’utilisation exige qu’un utilisateur soit avisé ou donne son consentement explicite avant qu’un accès délégué soit accordé, envisagez d’utiliser [Client Initiated Backchannel Authentication (CIBA)](/docs/fr-ca/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-ciba-flow) pour envoyer une demande de consentement à l’appareil de l’utilisateur final avant d’effectuer l’échange de jeton. Pour des besoins de notification plus simples, vous pouvez mettre en œuvre une logique de notification dans votre Action d’échange de jeton personnalisé, une Action Post-Login ou vos services en aval.
</Callout>

<div id="use-case-support-agent-accessing-a-web-application-on-behalf-of-an-end-user">
  ### Cas d’utilisation : un agent de soutien accède à une application Web au nom d’un utilisateur final
</div>

Il arrive qu’un agent de soutien doive reproduire un problème directement dans l’application Web de GearUp en agissant au nom de l’utilisateur final, plutôt que de simplement appeler l’API de GearUp. Au lieu d’échanger des jetons contre un jeton d’accès, l’outil de soutien demande un jeton de transfert de session, qu’il utilise pour établir une session Web déléguée pour l’utilisateur final dans l’application Web de GearUp.

<Frame>
  <img src="https://mintcdn.com/translations/oyf5dwefQkpFhecP/docs/images/custom-token-exchange/Support-agent-accessing-a-web-application-on-behalf-of-an-end-user.png?fit=max&auto=format&n=oyf5dwefQkpFhecP&q=85&s=fd76ef78c6b94393b1d1362ddfa93d73" alt="" width="1049" height="586" data-path="docs/images/custom-token-exchange/Support-agent-accessing-a-web-application-on-behalf-of-an-end-user.png" />
</Frame>

1. L’outil de soutien authentifie l’agent auprès d’Auth0 et obtient le jeton d’ID de l’agent.
2. L’outil de soutien envoie une requête au point de terminaison `/oauth/token` d’Auth0 à l’aide d’une [requête échange de jeton personnalisé](/docs/fr-ca/get-started/authentication-and-authorization-flow/token-exchange-flow/call-your-api-using-the-custom-token-exchange-flow), en définissant `audience` sur `urn:YOUR_AUTH0_TENANT_DOMAIN:session_transfer`, avec un JWT signé identifiant l’utilisateur final comme `subject_token` et le jeton d’ID de l’agent comme `actor_token`.
3. L’Action d’échange de jeton personnalisé associée valide le jeton du sujet, autorise la délégation et appelle `api.authentication.setActor()` et `api.authentication.setUserByConnection()` afin de sélectionner la connexion acceptée par l’application cible. L’appel à `setActor()` est requis lors de la demande d’un jeton de transfert de session; si vous l’omettez, une erreur `400` est renvoyée.
4. Auth0 émet un jeton de transfert de session (`issued_token_type: urn:auth0:params:oauth:token-type:session_transfer_token`) au lieu d’un jeton d’accès.
5. L’outil de soutien redirige le navigateur de l’agent vers l’application Web de GearUp en transmettant le jeton de transfert de session comme paramètre de requête dans son `initiate_login_uri`.
6. L’application Web de GearUp transmet le jeton de transfert de session dans sa propre requête au point de terminaison `/authorize` d’Auth0, qui valide le jeton et établit une session éphémère à durée limitée pour l’utilisateur final, l’agent étant enregistré comme acteur à des fins d’audit. Consultez [Implement Session Delegation](/docs/fr-ca/authenticate/single-sign-on/session-delegation/implement-session-delegation) pour obtenir tous les détails sur la requête et la réponse.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Votre application Web doit explicitement accepter les sessions déléguées, et plusieurs comportements de session diffèrent de ceux d’une connexion standard (durée de session, jetons d’actualisation, MFA, etc.). Consultez [Session Delegation](/docs/fr-ca/authenticate/single-sign-on/session-delegation) pour configurer votre application Web et comprendre ces comportements, ces limites et la façon d’auditer les sessions déléguées.
</Callout>

L’exemple de code suivant montre comment implémenter la délégation de session dans l’Action d’échange de jeton personnalisé. Nous vérifions l’élément `audience` demandé afin de distinguer une requête de délégation de session d’une simple requête d’accès à l’API et appliquons une politique d’autorisation distincte à chacune. Nous définissons ensuite l’acteur et l’utilisateur. Auth0 décide d’émettre un jeton d’accès ou un jeton de transfert de session en fonction de l’élément `audience` demandé, et non de la logique de l’Action.

```javascript lines expandable theme={null}
const jwksUri = "https://gearup.com/.well-known/jwks.json";

// L’audience est `urn:YOUR_AUTH0_TENANT_DOMAIN:session_transfer` — on vérifie le suffixe (et non
// l’égalité), car le préfixe de domaine varie selon le tenant.
const SESSION_TRANSFER_AUDIENCE_SUFFIX = ":session_transfer";

/**
 * Handler à exécuter lors du traitement d’une requête d’échange de jeton personnalisé
 * @param {Event} event - Détails de la requête d’échange de jetons entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires permettant de définir le processus d’échange de jetons.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {

  // 1. VALIDER le jeton d’utilisateur final reçu dans subject_token
  const { isValid, payload } = await validateToken(event.transaction.subject_token);

  if (!isValid) {
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
    return;
  }

  // 2. DÉTERMINER si cette requête concerne un jeton de transfert de session plutôt qu’un
  // jeton d’accès, afin d’appliquer une politique d’autorisation distincte.
  const isSessionDelegation = event.resource_server?.identifier?.endsWith(SESSION_TRANSFER_AUDIENCE_SUFFIX);

  // 3. AUTORISER l’acteur — vérifier que l’agent est autorisé à agir au nom de cet utilisateur final
  const actorUser = event.transaction.actor_token_user;
  if (!actorUser) {
    api.access.deny("invalid_request", "Actor token is required for this profile");
    return;
  }

  const isAuthorized = isSessionDelegation
    ? await checkSessionDelegationPolicy(actorUser, payload.sub)
    : await checkApiDelegationPolicy(actorUser, payload.sub);

  if (!isAuthorized) {
    api.access.deny("unauthorized_actor", "Agent is not authorized to act on behalf of this user");
    return;
  }

  // 4. DÉFINIR L’ACTEUR — Auth0 émet un jeton de transfert de session plutôt qu’un
  // jeton d’accès, car l’audience demandée est l’audience session_transfer.
  api.authentication.setActor({
    sub: actorUser.user_id,
    sub_profile: "human",
    role: "support"
  });

  // 5. DÉFINIR L’UTILISATEUR par connexion — le jeton de transfert de session est limité à
  // cette connexion précise; l’application cible ne peut donc l’échanger que si
  // cette connexion y est activée. Nous faisons confiance à l’application appelante pour
  // inclure la connexion voulue sous forme de claim personnalisé dans subject_token (elle
  // a signé ce jeton).
  if (!payload.connection) {
    api.access.deny("invalid_request", "subject_token missing connection claim");
    return;
  }
  api.authentication.setUserByConnection(payload.connection, { user_id: payload.sub }, {
    creationBehavior: "none",
    updateBehavior: "none"
  });

  async function validateToken(subjectToken) {
    // Ajoutez votre code ici. CONSULTEZ LES EXEMPLES DE CODE POUR DES EXEMPLES DÉTAILLÉS
  }

  async function checkApiDelegationPolicy(agent, userId) {
    // Implémentez ici la vérification de votre politique de délégation d’accès à l’API.
  }

  async function checkSessionDelegationPolicy(agent, userId) {
    // Implémentez ici la vérification de votre politique de délégation de session.
  }
};
```

<div id="important-considerations-for-session-delegation">
  #### Considérations importantes concernant la délégation de session
</div>

En plus des [considérations relatives à l’autorisation déléguée](#important-considerations-for-delegated-authorization), tenez compte des éléments suivants :

* Détectez les requêtes de délégation de session à l’aide de l’audience `session_transfer` et appliquez une politique d’autorisation propre à ce cas.
* L’accessibilité de l’application cible dépend de la connexion précise configurée par votre Action.
* Pour configurer la connexion, l’application qui appelle l’Échange de jetons personnalisé doit avoir accès aux mêmes connexions que vos applications cibles.
* Utilisez des domaines Auth0 distincts pour les applications initiatrice et cible afin d’éviter qu’une session existante sur un domaine partagé bloque la session déléguée.
* Les agents doivent se déconnecter entre les sessions déléguées de différents utilisateurs.

Consultez [Implement Session Delegation](/docs/fr-ca/authenticate/single-sign-on/session-delegation/implement-session-delegation) pour connaître tous les détails de la mise en œuvre de bout en bout, y compris ces considérations.

<div id="code-samples">
  ## Exemples de code
</div>

Les exemples de code suivants présentent les bonnes pratiques pour les scénarios courants de validation des jetons de sujet entrants de façon sécurisée et efficace.

Utilisez des algorithmes et des clés asymétriques chaque fois que possible, puisque vous n’avez alors aucun secret à partager avec Auth0. Cela simplifie aussi la rotation des clés, par exemple lorsque vous exposez un endpoint URI JWKS pour annoncer les clés publiques applicables.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Il vous revient de vous assurer que les jetons de sujet sont protégés par un algorithme robuste et des clés/secrets offrant une entropie suffisante.
</Callout>

<div id="validate-jwts-signed-with-asymmetric-keys">
  ### Valider les JWTs signés avec des clés asymétriques
</div>

Tenez compte des recommandations suivantes :

* Utilisez les méthodes [`api.cache ()`](/docs/fr-ca/customize/actions/explore-triggers/signup-and-login-triggers/custom-token-exchange-trigger/custom-token-exchange-api-object) d’Actions pour éviter d’avoir à récupérer les clés de signature pour chaque transaction.
* Respectez les pratiques exemplaires de la [RFC8725](https://www.rfc-editor.org/rfc/rfc8725.txt)
* Utilisez les algorithmes RS\*, PS\*, ES\* ou Ed25519
* N’utilisez pas et n’acceptez pas l’algorithme none
* Utilisez RSA avec une taille minimale de 2048 bits.

```javascript lines expandable theme={null}
const { jwtVerify, importJWK } = require("jose");

const jwksUri = "https://example.com/.well-known/jwks.json";
const fetchTimeout = 5000; // 5 secondes

const validIssuer = "urn:my-issuer"; // Remplacez par votre émetteur

/**
 * Handler à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {
  const { isValid, payload } = await validateToken(
    event.transaction.subject_token,
  );

  // Appliquez votre politique d'autorisation au besoin pour déterminer si la demande est valide.
  // Utilisez api.access.deny() pour rejeter la transaction dans ces cas.

  if (!isValid) {
    // Marquer le subject token comme invalide et faire échouer la transaction.
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
  } else {
    // Définir l'utilisateur de la demande actuelle comme authentifié, en utilisant l'ID utilisateur du subject token.
    api.authentication.setUserById(payload.sub);
  }

  /**
   * Valider le subject token
   * @param {string} subjectToken
   * @returns {Promise<{ isValid: boolean, payload?: object }>} Payload du jeton
   */
  async function validateToken(subjectToken) {
    try {
      const { payload, protectedHeader } = await jwtVerify(
        subjectToken,
        async (header) => await getPublicKey(header.kid),
        {
          issuer: validIssuer,
        },
      );

      // Effectuer une validation supplémentaire sur le payload du jeton au besoin

      return { isValid: true, payload };
    } catch (/** @type {any} */ error) {
      if (error.message === "Error fetching JWKS") {
        throw new Error("Internal error - retry later");
      } else {
        console.log("Token validation failed:", error.message);
        return { isValid: false };
      }
    }
  }

  /**
   * Obtenir la clé publique à utiliser pour la vérification. Charger depuis le cache des actions si disponible, sinon
   * récupérer la clé depuis l'endpoint JWKS et la stocker dans le cache.
   * @param {string} kid - kid (Key ID) de la clé à utiliser pour la vérification
   * @returns {Promise<Object>}
   */
  async function getPublicKey(kid) {
    const cachedKey = api.cache.get(kid);
    let keyData;

    if (!cachedKey) {
      console.log(`Clé ${kid} introuvable dans le cache`);
      keyData = await fetchKeyFromJWKS(kid);
      // Mettre en cache la version sérialisée en chaîne
      api.cache.set(kid, JSON.stringify(keyData), { ttl: 600000 });
    } else {
      // Analyser l'objet JWK brut depuis le cache
      keyData = JSON.parse(cachedKey.value);
    }

    //Convertir l'objet JWK brut en objet KeyLike
    return await importJWK(keyData, keyData.alg);
  }

  /**
   * Récupérer la clé de signature publique depuis l'endpoint JWKS fourni, pour la vérification du jeton
   * @param {string} kid - kid (Key ID) de la clé à utiliser pour la vérification
   * @returns {Promise<object>}
   */
  async function fetchKeyFromJWKS(kid) {
    const controller = new AbortController();
    setTimeout(() => controller.abort(), fetchTimeout);

    /** @type {any} */
    const response = await fetch(jwksUri);

    if (!response.ok) {
      console.log(`Erreur lors de la récupération du JWKS. Statut de la réponse : ${response.status}`);
      throw new Error("Error fetching JWKS");
    }
    const jwks = await response.json();
    const key = jwks.keys.find((key) => key.kid === kid);
    if (!key) {
      throw new Error("Key not found in JWKS");
    }
    return key;
  }
};
```

<div id="validate-jwts-signed-with-symmetric-keys">
  ### Valider les JWT signés avec des clés symétriques
</div>

Tenez compte des recommandations suivantes :

* Utilisez les [Secrets des Actions](/docs/fr-ca/customize/actions/write-your-first-action#add-a-secret) pour stocker vos secrets symétriques en toute sécurité.
* Suivez les bonnes pratiques de la [RFC8725](https://www.rfc-editor.org/rfc/rfc8725.txt)
* Utilisez des algorithmes sécuritaires comme HS256, ainsi que des secrets aléatoires à entropie élevée (par exemple, d’au moins 256 bits)

```javascript lines expandable theme={null}
const { jwtVerify } = require("jose");

const validIssuer = "urn:my-issuer"; // Remplacez par votre émetteur

/**
 * Gestionnaire à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {
  // Initialiser la clé symétrique partagée à partir des secrets des Actions
  const encoder = new TextEncoder();
  const symmetricKey = encoder.encode(event.secrets.SHARED_SECRET);

  const { isValid, payload } = await validateToken(
    event.transaction.subject_token,
    symmetricKey,
  );

  // Appliquez votre politique d'autorisation au besoin pour déterminer si la demande est valide.
  // Utilisez api.access.deny() pour rejeter la transaction dans ces cas.

  if (!isValid) {
    // Marquer le subject token comme invalide et faire échouer la transaction.
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
  } else {
    // Définir l'utilisateur de la demande actuelle comme authentifié, en utilisant l'ID utilisateur du subject token.
    api.authentication.setUserById(payload.sub);
  }
};

/**
 * Valider le subject token
 * @param {string} subjectToken
 * @param {Uint8Array} symmetricKey
 * @returns {Promise<{ isValid: boolean, payload?: object }>} Contenu du jeton
 */
async function validateToken(subjectToken, symmetricKey) {
  try {
    // Valider que le jeton est correctement signé avec la clé symétrique partagée
    // Vérifie également qu'il n'est pas expiré, pourvu qu'il inclue un attribut 'exp'.
    const { payload, protectedHeader } = await jwtVerify(
      subjectToken,
      symmetricKey,
      {
        issuer: validIssuer,
      },
    );

    return { isValid: true, payload };
  } catch (/** @type {any} */ error) {
    console.log("Token validation failed:", error.message);
    return { isValid: false };
  }
}
```

<div id="validate-opaque-token-with-an-external-service">
  ### Valider un jeton opaque avec un service externe
</div>

Utilisez les [Action Secrets](/docs/fr-ca/customize/actions/write-your-first-action#add-a-secret) pour stocker de façon sécuritaire le <Tooltip tip="Secret client : secret utilisé par un client (application) pour s’authentifier auprès du serveur d’autorisation; il ne doit être connu que du client et du serveur d’autorisation et doit être suffisamment aléatoire pour ne pas pouvoir être deviné." cta="Voir le glossaire" href="/docs/fr-ca/glossary?term=client+secret">secret client</Tooltip> de votre IdP externe.

```javascript lines expandable theme={null}
const tokenEndpoint = "EXTERNAL_TOKEN_ ENDPOINT";
const userInfoEndpoint = "EXTERNAL_USER_INFO_ENDPOINT";
const clientId = "EXTERNAL_CLIENT_ID";
const connectionName = "YOUR_CONNECTION_NAME";
const fetchTimeout = 5000; // 5 secondes

/**
 * Handler à exécuter lors d'une demande d'échange de jeton personnalisé
 * @param {Event} event - Détails sur la demande d'échange de jeton entrante.
 * @param {CustomTokenExchangeAPI} api - Méthodes et utilitaires pour définir le processus d'échange de jeton.
 */
exports.onExecuteCustomTokenExchange = async (event, api) => {
  const { isValid, user } = await getUserProfile(
    event.transaction.subject_token,
    event.secrets.CLIENT_SECRET,
  );

  if (!isValid) {
    // Marquer le subject token comme invalide et faire échouer la transaction.
    api.access.rejectInvalidSubjectToken("Invalid subject_token");
    return;
  }

  // Appliquez votre politique d'autorisation au besoin pour déterminer si la demande est valide.
  // Utilisez api.access.deny() pour rejeter la transaction dans ces cas.

  // Une fois le profil obtenu, on définit l'utilisateur dans la connexion cible
  api.authentication.setUserByConnection(
    connectionName,
    {
      // seul le user_id dans la connexion est nécessaire, car nous ne
      // créons ni ne mettons à jour l'utilisateur
      user_id: user.sub,
    },
    {
      creationBehavior: "none",
      updateBehavior: "none",
    },
  );
};

/**
 * Échanger le jeton d’actualisation et charger le profil utilisateur depuis l'IdP hérité
 * @param {string} refreshToken
 * @param {string} clientSecret
 * @returns {Promise<{ isValid: boolean, user?: object }>} Si le jeton d’actualisation a été échangé avec succès, retourne le profil utilisateur
 */
async function getUserProfile(refreshToken, clientSecret) {
  const { isValid, accessToken } = await refreshAccessToken(
    refreshToken,
    clientSecret,
  );
  if (!isValid) {
    return { isValid: false };
  }

  const controller = new AbortController();
  setTimeout(() => controller.abort(), fetchTimeout);

  /** @type {any} */
  const response = await fetch(userInfoEndpoint, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
  });

  if (!response.ok) {
    console.log(`Failed to fetch user info. Status: ${response.status}`);
    throw new Error("Error fetching user info");
  }

  const userProfile = await response.json();

  return { isValid: true, user: userProfile };
}

/**
 * Utiliser le jeton d’actualisation avec l'IdP hérité pour le valider et obtenir un jeton d’accès
 * @param {string} refreshToken
 * @param {string} clientSecret
 * @returns {Promise<{ isValid: boolean, accessToken?: string }>} Si le jeton d’actualisation a été échangé avec succès, retourne le jeton d’accès
 */
async function refreshAccessToken(refreshToken, clientSecret) {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), fetchTimeout);

  /** @type {any} */
  let response;

  try {
    response = await fetch(tokenEndpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        grant_type: "refresh_token",
        refresh_token: refreshToken,
        client_id: clientId,
        client_secret: clientSecret,
      }).toString(),
    });
  } catch (error) {
    console.error("Error refreshing token");
    throw error;
  }

  if (!response.ok) {
    const errorBody = await response.json();
    console.error("Error refreshing token:", errorBody.error);

    // Si nous recevons une erreur indiquant que le jeton d’actualisation est invalide (par exemple, une erreur invalid_grant),
    // nous devons explicitement signaler un jeton invalide à l'aide de api.access.rejectInvalidSubjectToken
    // pour prévenir les attaques par force brute sur le jeton d’actualisation en activant Suspicious IP Throttling.
    // Pour les autres erreurs indiquant une erreur générique lors de la demande à l'IdP, nous devons lever
    // une erreur pour signaler une défaillance transitoire.
    if (errorBody.error === "invalid_grant") {
      return { isValid: false };
    } else {
      throw new Error("Error refreshing token");
    }
  }

  // Analyser la réponse, sous la forme { access_token: "...", expires_in: ..., }
  const data = await response.json();
  console.log("Successfully exchanged refresh token");
  return { isValid: true, accessToken: data.access_token };
}
```
