Partager via


Bonnes pratiques en matière d’informations d’identification

Cet article fournit les meilleures pratiques pour la gestion des jetons d’accès utilisateur dans les kits SDK Azure Communication Services. Suivez ces instructions pour optimiser les ressources utilisées par votre application et réduire le nombre d’allers-retours vers l’API Azure Communication Identity.

Communication Token Credential

Communication Token Credential (Credential) est une authentification primitive qui wrappe des jetons d’accès utilisateur. Il est utilisé pour authentifier les utilisateurs dans Communication Services, tels que conversation ou appel. En outre, il fournit des fonctionnalités d’actualisation de jeton intégrées pour la commodité du développeur.

Choix de la durée de vie de la session

Selon votre scénario, vous souhaiterez peut-être ajuster la durée de vie des jetons émis pour votre application. Les meilleures pratiques suivantes ou leur combinaison peuvent vous aider à obtenir la solution optimale pour votre scénario :

Définition d’un délai d’expiration de jeton personnalisé

Lors de la demande d’un nouveau jeton, nous vous recommandons d’utiliser des jetons de durée de vie courte pour les messages de conversation ponctuels ou les sessions d’appel limitées dans le temps. Nous recommandons des jetons de durée de vie plus longs pour les agents qui utilisent l’application pendant des périodes plus longues. Le délai d’expiration du jeton par défaut est de 24 heures. Vous pouvez personnaliser l’heure d’expiration en fournissant une valeur comprise entre une heure et 24 heures au paramètre facultatif comme suit :

const tokenOptions = { tokenExpiresInMinutes: 60 };
const user = { communicationUserId: userId };
const scopes = ["chat"];
let communicationIdentityToken = await identityClient.getToken(user, scopes, tokenOptions);

Jeton statique

Pour les clients avec une durée de vie courte, initialisez Credential avec un jeton statique. Cette approche convient aux scénarios tels que l’envoi de messages de conversation ponctuels ou de sessions d’appel limitées dans le temps.

let communicationIdentityToken = await identityClient.getToken({ communicationUserId: userId }, ["chat", "voip"]);
const tokenCredential = new AzureCommunicationTokenCredential(communicationIdentityToken.token);

Fonction de rappel

Pour les clients avec une durée de vie longue, initialisez Credential avec une fonction de rappel qui garantit un état d’authentification continu pendant les communications. Cette approche convient, par exemple, pour les sessions d’appel longues.

const tokenCredential = new AzureCommunicationTokenCredential({
            tokenRefresher: async (abortSignal) => fetchTokenFromMyServerForUser(abortSignal, "<user_name>")
        });

Actualisation de jeton

Pour implémenter correctement le rappel de rafraîchissement du jeton, le code doit retourner une chaîne avec un Jeton Web JSON valide (JWT). Le jeton retourné doit toujours être valide et sa date d’expiration définie ultérieurement. Certaines plateformes, telles que JavaScript et .NET, offrent un moyen d’abandonner l’opération d’actualisation et de passer AbortSignal ou CancellationToken à votre fonction. Nous vous recommandons d’accepter ces objets, de les utiliser ou de les transmettre.

Exemple 1 : Actualisation d’un jeton pour un utilisateur de communication

Supposons que nous disposons d’une application Node.js basée sur Express avec le /getToken point de terminaison, ce qui permet d’extraire un nouveau jeton valide pour un utilisateur spécifié par son nom.

app.post('/getToken', async (req, res) => {
    // Custom logic to determine the communication user id
    let userId = await getCommunicationUserIdFromDb(req.body.username);
    // Get a fresh token
    const identityClient = new CommunicationIdentityClient("<COMMUNICATION_SERVICES_CONNECTION_STRING>");
    let communicationIdentityToken = await identityClient.getToken({ communicationUserId: userId }, ["chat", "voip"]);
    res.json({ communicationIdentityToken: communicationIdentityToken.token });
});

Ensuite, nous devons implémenter un rappel d’actualisation de jeton dans l’application cliente, en utilisant AbortSignal de manière appropriée et en retournant une chaîne JWT déwrappée.

const fetchTokenFromMyServerForUser = async function (abortSignal, username) {
    const response = await fetch(`${HOST_URI}/getToken`,
        {
            method: "POST",
            body: JSON.stringify({ username: username }),
            signal: abortSignal,
            headers: { 'Content-Type': 'application/json' }
        });

    if (response.ok) {
        const data = await response.json();
        return data.communicationIdentityToken;
    }
};

Exemple 2 : Actualisation d’un jeton pour un utilisateur Teams

Supposons que nous disposons d’une application Node.js basée sur Express avec le /getTokenForTeamsUser point de terminaison permettant d’échanger un jeton d’accès Microsoft Entra d’un utilisateur Teams pour un nouveau jeton d’accès Communication Identity avec une heure d’expiration correspondante.

app.post('/getTokenForTeamsUser', async (req, res) => {
    const identityClient = new CommunicationIdentityClient("<COMMUNICATION_SERVICES_CONNECTION_STRING>");
    let communicationIdentityToken = await identityClient.getTokenForTeamsUser(req.body.teamsToken, '<AAD_CLIENT_ID>', '<TEAMS_USER_OBJECT_ID>');
    res.json({ communicationIdentityToken: communicationIdentityToken.token });
});

Ensuite, nous devons implémenter un rappel d’actualisation de jeton dans l’application cliente, dont la responsabilité est de :

  1. Actualisez le jeton d’accès Microsoft Entra de l’utilisateur Teams.
  2. Échangez le jeton d’accès Microsoft Entra de l’utilisateur Teams contre un jeton d’accès Communication Identity.
const fetchTokenFromMyServerForUser = async function (abortSignal, username) {
    // 1. Refresh the Azure AD access token of the Teams User
    let teamsTokenResponse = await refreshAadToken(abortSignal, username);

    // 2. Exchange the Azure AD access token of the Teams User for a Communication Identity access token
    const response = await fetch(`${HOST_URI}/getTokenForTeamsUser`,
        {
            method: "POST",
            body: JSON.stringify({ teamsToken: teamsTokenResponse.accessToken }),
            signal: abortSignal,
            headers: { 'Content-Type': 'application/json' }
        });

    if (response.ok) {
        const data = await response.json();
        return data.communicationIdentityToken;
    }
}

Dans cet exemple, nous utilisons la bibliothèque d’authentification Microsoft (MSAL) pour actualiser le jeton d’accès Microsoft Entra. En suivant le guide d’acquisition d’un jeton Microsoft Entra pour appeler une API, nous essayons d’abord d’obtenir le jeton sans l’interaction de l’utilisateur. Si cela n’est pas possible, nous déclenchons l’un des flux interactifs.

const refreshAadToken = async function (abortSignal, username) {
    if (abortSignal.aborted === true) throw new Error("Operation canceled");

    // MSAL.js v2 exposes several account APIs; the logic to determine which account to use is the responsibility of the developer. 
    // In this case, we'll use an account from the cache.    
    let account = (await publicClientApplication.getTokenCache().getAllAccounts()).find(u => u.username === username);

    const renewRequest = {
        scopes: [
            "https://auth.msft.communication.azure.com/Teams.ManageCalls",
            "https://auth.msft.communication.azure.com/Teams.ManageChats"
        ],
        account: account,
        forceRefresh: forceRefresh
    };
    let tokenResponse = null;
    // Try to get the token silently without the user's interaction    
    await publicClientApplication.acquireTokenSilent(renewRequest).then(renewResponse => {
        tokenResponse = renewResponse;
    }).catch(async (error) => {
        // In case of an InteractionRequired error, send the same request in an interactive call
        if (error instanceof InteractionRequiredAuthError) {
            // You can choose the popup or redirect experience (`acquireTokenPopup` or `acquireTokenRedirect` respectively)
            publicClientApplication.acquireTokenPopup(renewRequest).then(function (renewInteractiveResponse) {
                tokenResponse = renewInteractiveResponse;
            }).catch(function (interactiveError) {
                console.log(interactiveError);
            });
        }
    });
    return tokenResponse;
}

Fourniture d’un jeton initial

Pour optimiser davantage votre code, vous pouvez récupérer le jeton au démarrage de l’application et le transmettre directement aux informations d’identification. Quand vous fournissez un jeton initial, le premier appel à la fonction de rappel d’actualisation est ignoré, mais tous les appels ultérieurs sont maintenus.

const tokenCredential = new AzureCommunicationTokenCredential({
            tokenRefresher: async () => fetchTokenFromMyServerForUser("<user_id>"),
            token: "<initial_token>"
        });

Actualisation de jeton proactive

Utilisez l’actualisation proactive pour éliminer tout délai possible pendant la récupération à la demande du jeton. L’actualisation proactive met à jour le jeton en arrière-plan à la fin de sa durée de vie. Lorsque le jeton est sur le point d’expirer, 10 minutes avant la fin de sa validité, le système de gestion des identifiants commence à essayer de récupérer le jeton. Il déclenche le rappel d’actualisation avec une fréquence de plus en plus rapide jusqu’à ce qu’il parvienne à récupérer un jeton avec une validité suffisamment longue.

const tokenCredential = new AzureCommunicationTokenCredential({
            tokenRefresher: async () => fetchTokenFromMyServerForUser("<user_id>"),
            refreshProactively: true
        });

Si vous souhaitez annuler les tâches d’actualisation planifiées, supprimez l’objet Credential.

Actualisation proactive d’un jeton pour un utilisateur Teams

Pour réduire le nombre d’allers-retours vers l’API Azure Communication Identity, assurez-vous que le jeton Microsoft Entra que vous passez pour un échange a suffisamment de validité (> 10 minutes). Si MSAL retourne un jeton mis en cache avec une validité plus courte, vous avez les options suivantes pour contourner le cache :

  1. Actualiser de force le jeton
  2. Augmenter la fenêtre de renouvellement du jeton MSAL à plus de 10 minutes

Option 1 : Activer le flux d’acquisition des jetons avec AuthenticationParameters.forceRefresh défini sur true.

// Extend the `refreshAadToken` function 
const refreshAadToken = async function (abortSignal, username) {

    // ... existing refresh logic

    // Make sure the token has at least 10-minute lifetime and if not, force-renew it
    if (tokenResponse.expiresOn < (Date.now() + (10 * 60 * 1000))) {
        const renewRequest = {
            scopes: [
                "https://auth.msft.communication.azure.com/Teams.ManageCalls",
                "https://auth.msft.communication.azure.com/Teams.ManageChats"
            ],
            account: account,
            forceRefresh: true // Force-refresh the token
        };        
        
        await publicClientApplication.acquireTokenSilent(renewRequest).then(renewResponse => {
            tokenResponse = renewResponse;
        });
    }
}

Option 2 : Initialisez le contexte d’authentification MSAL en instanciant un PublicClientApplication avec un SystemOptions.tokenRenewalOffsetSeconds personnalisé.

const publicClientApplication = new PublicClientApplication({
    system: {
        tokenRenewalOffsetSeconds: 900 // 15 minutes (by default 5 minutes)
    });

Annulation de l’actualisation

Pour que les clients Communication puissent annuler les tâches d’actualisation en cours, il faut passer un objet d’annulation au rappel d’actualisation. Ce modèle s’applique uniquement à JavaScript et .NET.

var controller = new AbortController();

var joinChatBtn = document.querySelector('.joinChat');
var leaveChatBtn = document.querySelector('.leaveChat');

joinChatBtn.addEventListener('click', function () {
    // Wrong:
    const tokenCredentialWrong = new AzureCommunicationTokenCredential({
        tokenRefresher: async () => fetchTokenFromMyServerForUser("<user_name>")
    });

    // Correct: Pass abortSignal through the arrow function
    const tokenCredential = new AzureCommunicationTokenCredential({
        tokenRefresher: async (abortSignal) => fetchTokenFromMyServerForUser(abortSignal, "<user_name>")
    });

    // ChatClient is now able to abort token refresh tasks
    const chatClient = new ChatClient("<endpoint-url>", tokenCredential);

    // Pass the abortSignal to the chat client through options
    const createChatThreadResult = await chatClient.createChatThread(
        { topic: "Hello, World!" },
        {
            // ...
            abortSignal: controller.signal
        }
    );

    // ...
});

leaveChatBtn.addEventListener('click', function() {
    controller.abort();
    console.log('Leaving chat...');
});

Si vous souhaitez annuler les tâches d’actualisation suivantes, supprimez l’objet Credential.

Nettoyage des ressources

Étant donné que l’objet Credential peut être transmis à plusieurs instances de client Chat ou Appelant, le SDK ne fait aucune hypothèse sur sa durée de vie et laisse la responsabilité de son élimination au développeur. Il appartient aux applications Communication Services de supprimer l’instance d’informations d’identification lorsqu’elle n’est plus nécessaire. La suppression des informations d’identification annule également les actions d’actualisation planifiées lorsque l’actualisation proactive est activée.

Appelez la fonction .dispose().

const tokenCredential = new AzureCommunicationTokenCredential("<token>");
// Use the credential for Calling or Chat
const chatClient = new ChatClient("<endpoint-url>", tokenCredential);
// ...
tokenCredential.dispose()

Gestion d’une déconnexion

Selon votre scénario, vous souhaiterez peut-être déconnecter un utilisateur d’un ou plusieurs services :

  • Pour déconnecter un utilisateur d’un seul service, supprimez l’objet Credential.
  • Pour déconnecter un utilisateur de plusieurs services, implémentez un mécanisme de signalisation pour avertir tous les services de supprimer l’objet Credential, et en outre, révoquer tous les jetons d’accès pour une identité donnée.

Étapes suivantes

Cet article a décrit comment :

  • Initialiser et supprimer correctement un objet Credential
  • Implémenter un rappel d’actualisation de jeton
  • Optimiser votre logique d’actualisation de jeton