Compartir a través de


Procedimientos recomendados sobre credenciales

En este artículo se proporcionan procedimientos recomendados para administrar tokens de acceso de usuario en los SDK de Azure Communication Services. Siga estas instrucciones para optimizar los recursos usados por la aplicación y reducir el número de recorridos de ida y vuelta a Azure Communication Identity API.

Credencial de token de Communication

La credencial de token de Communication (credencial) es un tipo primitivo de autenticación que encapsula los tokens de acceso de usuario. Se usa para autenticar a los usuarios en Communication Services, como Chat o Llamadas. Además, proporciona una funcionalidad integrada de actualización de tokens para la comodidad del desarrollador.

Elección de la duración de la sesión

En función de su escenario, es posible que desee ajustar la duración de los tokens emitidos para la aplicación. Los siguientes procedimientos recomendados o su combinación pueden ayudarle a lograr la solución óptima para su escenario:

Establecimiento de una hora de expiración del token personalizado

Al solicitar un nuevo token, se recomienda usar tokens de duración corta para mensajes de chat únicos o sesiones de llamada con tiempo limitado. Se recomiendan tokens de duración más largos para los agentes que usan la aplicación durante períodos de tiempo más largos. El tiempo de expiración del token predeterminado es de 24 horas. Puede personalizar la hora de expiración proporcionando un valor entre una hora y 24 horas para el parámetro opcional de la siguiente manera:

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

Token estático

Para los clientes de corta duración, inicialice la credencial con un token estático. Este enfoque es adecuado para escenarios como el envío de mensajes de chat de un solo uso o sesiones de llamadas con tiempo limitado.

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

Función de devolución de llamada

Para los clientes de larga duración, inicialice la credencial con una función de devolución de llamada que garantice un estado de autenticación continua durante las comunicaciones. Este enfoque es adecuado, por ejemplo, para sesiones largas de llamadas.

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

Actualización del token

Para implementar correctamente el callback de actualización de tokens, el código debe devolver una cadena con un token web JSON (JWT) válido. El token devuelto siempre debe ser válido y su fecha de expiración establecida en el futuro. Algunas plataformas, como JavaScript y .NET, ofrecen una manera de anular la operación de actualización y pasar AbortSignal o CancellationToken a su función. Se recomienda aceptar estos objetos, usarlos o pasarlos más adelante.

Ejemplo 1: Actualización de un token para un usuario de comunicación

Supongamos que tenemos una aplicación Node.js creada en Express con el /getToken punto de conexión que permite capturar un nuevo token válido para un usuario especificado por nombre.

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 });
});

A continuación, es necesario implementar una devolución de llamada del actualizador de tokens en la aplicación cliente, utilizando correctamente AbortSignal y devolviendo una cadena JWT desencapsulada.

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;
    }
};

Ejemplo 2: Actualización de un token para un usuario de Teams

Supongamos que tenemos una aplicación de Node.js integrada en Express con el punto de conexión /getTokenForTeamsUser que permite intercambiar un token de acceso de Microsoft Entra de un usuario de Teams por un nuevo token de acceso de Communication Identity con una hora de expiración correspondiente.

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 });
});

A continuación, debemos implementar un callback de refresco de tokens en la aplicación cliente, cuya responsabilidad es:

  1. Actualice el token de acceso de Microsoft Entra del usuario de Teams.
  2. Intercambiar el token de acceso de Microsoft Entra del usuario de Teams por un token de acceso de identidad comunicativa.
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;
    }
}

En este ejemplo, usamos la Biblioteca de autenticación de Microsoft (MSAL) para actualizar el token de acceso de Microsoft Entra. Siguiendo la guía para adquirir un token de Microsoft Entra para llamar a una API, primero intentamos obtener el token sin la interacción del usuario. Si no es posible, desencadenamos uno de los flujos interactivos.

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;
}

Proporcionar un token inicial

Para optimizar aún más el código, puede capturar el token en el inicio de la aplicación y pasarlo directamente a la credencial. Al proporcionar un token inicial, se omite la primera llamada a la función de devolución de llamada del actualizador, a la vez que se conservan todas las llamadas posteriores a ella.

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

Actualización proactiva del token

Usar la actualización proactiva para eliminar cualquier retraso posible durante la obtención bajo demanda del token. La actualización proactiva actualiza el token en segundo plano al final de su vigencia. Cuando el token está a punto de expirar, 10 minutos antes del final de su validez, la credencial comienza a intentar recuperar el token. Desencadena la devolución de llamada del actualizador con una frecuencia creciente hasta que se realiza correctamente y recupera un token con suficiente validez.

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

Si desea cancelar las tareas de actualización programadas, elimine el objeto Credential.

Actualización proactiva de un token para un usuario de Teams

Para minimizar el número de ciclos de ida y vuelta con la API de Azure Communication Identity, asegúrese de que el token de Microsoft Entra que está pasando para un intercambio tiene una validez suficientemente larga (> 10 minutos). En caso de que MSAL devuelva un token almacenado en caché con una validez más corta, tiene las siguientes opciones para omitir la memoria caché:

  1. Actualizar el token de forma forzada
  2. Aumentar la ventana de renovación de tokens de MSAL a más de 10 minutos

Opción 1: desencadene el flujo de adquisición de tokens con AuthenticationParameters.forceRefresh establecido en 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;
        });
    }
}

Opción 2: Inicialice el contexto de autenticación de MSAL instanciando un PublicClientApplication con un SystemOptions.tokenRenewalOffsetSeconds personalizado.

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

Cancelación de la actualización

Para que los clientes de comunicación puedan cancelar las tareas de actualización en curso, es necesario pasar un objeto de cancelación a la devolución de llamada del actualizador. Este patrón solo se aplica a JavaScript y .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 desea cancelar las tareas de actualización posteriores, elimine el objeto Credential.

Limpiar recursos

Dado que el objeto Credential se puede pasar a varias instancias de cliente de chat o llamada, el SDK no supone su duración y deja la responsabilidad de su eliminación al desarrollador. Es necesario que las aplicaciones de Communication Services eliminen la instancia de credencial cuando ya no sea necesaria. La eliminación de la credencial también cancela las acciones de actualización programadas cuando se habilita la actualización proactiva.

Llame a la función .dispose().

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

Gestión de cierre de sesión

En función de su escenario, puede que desee cerrar la sesión de un usuario desde uno o varios servicios:

  • Para cerrar la sesión de un usuario desde un único servicio, elimine el objeto Credential.
  • Para cerrar la sesión de un usuario desde varios servicios, implemente un mecanismo de señalización para notificar a todos los servicios que eliminen el objeto Credential y, además, revoque todos los tokens de acceso para una identidad determinada.

Pasos siguientes

En este artículo se describe cómo:

  • Inicialización y eliminación correcta de un objeto Credential
  • Implementar una devolución de llamada del actualizador de tokens
  • Optimizar la lógica de actualización de tokens