Partilhar via


Práticas recomendadas de credenciais

Este artigo fornece práticas recomendadas para gerenciar Tokens de Acesso de Usuário em SDKs dos Serviços de Comunicação do Azure. Siga estas orientações para otimizar os recursos usados pelo seu aplicativo e reduzir o número de viagens de ida e volta para a API de Identidade de Comunicação do Azure.

Credencial de Token de Comunicação

A Credencial de Token de Comunicação (Credencial) é uma primitiva de autenticação que encapsula Tokens de Acesso de Usuário. Ele é usado para autenticar usuários em Serviços de Comunicação, como Chat ou Chamada. Além disso, ele fornece funcionalidade de atualização de token integrada para a conveniência do desenvolvedor.

Escolhendo o tempo de vida da sessão

Dependendo do seu cenário, você pode querer ajustar a vida útil dos tokens emitidos para seu aplicativo. As seguintes práticas recomendadas ou sua combinação podem ajudá-lo a obter a solução ideal para o seu cenário:

Definindo um tempo de expiração de token personalizado

Ao solicitar um novo token, recomendamos o uso de tokens de curta duração para mensagens de bate-papo únicas ou sessões de chamada por tempo limitado. Recomendamos tokens de vida útil mais longos para agentes que usam o aplicativo por períodos de tempo mais longos. O tempo de expiração padrão do token é de 24 horas. Você pode personalizar o tempo de expiração fornecendo um valor entre uma hora e 24 horas para o parâmetro opcional da seguinte maneira:

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

Token estático

Para clientes de curta duração, inicialize a credencial com um token estático. Essa abordagem é adequada para cenários como o envio de mensagens de bate-papo pontuais ou sessões de chamada por tempo limitado.

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

Função de retorno de chamada

Para clientes de longa duração, inicialize a credencial com uma função de 'callback' que garante um estado de autenticação contínua durante as comunicações. Esta abordagem é adequada, por exemplo, para sessões longas de chamadas.

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

Atualização de token

Para implementar corretamente o retorno de chamada de atualização de token, o código deve retornar uma cadeia de caracteres com um JSON Web Token (JWT) válido. O token devolvido deve ser sempre válido e sua data de expiração definida no futuro. Algumas plataformas, como JavaScript e .NET, oferecem uma maneira de abortar a operação de atualização e passar AbortSignal ou CancellationToken para sua função. Recomendamos que você aceite esses objetos, utilize-os ou passe-os adiante.

Exemplo 1: Atualizando um token para um usuário de comunicação

Vamos supor que temos uma aplicação Node.js construída em Express com o /getToken endpoint que permite obter um novo token válido para um utilizador especificado pelo nome.

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

Em seguida, precisamos implementar um callback de atualização de token no aplicativo cliente, utilizando corretamente o AbortSignal e retornando uma cadeia de caracteres JWT desempacotada.

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

Exemplo 2: Atualizando um token para um usuário do Teams

Vamos supor que temos uma aplicação Node.js construída no Express com o /getTokenForTeamsUser endpoint que permite a troca de um token de acesso Microsoft Entra de um utilizador do Teams por um novo token de acesso de Identidade de Comunicação com um tempo de expiração correspondente.

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

Em seguida, precisamos implementar uma função de atualização de token no aplicativo cliente, cuja responsabilidade é:

  1. Atualize o token de acesso do Microsoft Entra do utilizador do Teams.
  2. Substitua o token de acesso do Microsoft Entra do utilizador do Teams por um token de acesso da Identidade de Comunicação.
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;
    }
}

Neste exemplo, usamos a Biblioteca de Autenticação da Microsoft (MSAL) para atualizar o token de acesso do Microsoft Entra. Seguindo o guia para adquirir um token Microsoft Entra para chamar uma API, primeiro tentamos obter o token sem a interação do usuário. Se isso não for possível, acionamos um dos fluxos interativos.

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

Fornecendo um token inicial

Para otimizar ainda mais seu código, você pode buscar o token na inicialização do aplicativo e passá-lo diretamente para a credencial. Ao fornecer um token inicial, ignora-se a primeira chamada para o callback de atualização, preservando todas as chamadas subsequentes.

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

Atualização proativa de tokens

Utilize uma atualização pro-ativa para eliminar qualquer possível atraso durante a obtenção sob demanda do token. A atualização proativa atualiza o token em segundo plano no final de sua vida útil. Quando o token está prestes a expirar, 10 minutos antes do final de sua validade, a Credencial começa a tentar recuperá-lo. Ele aciona o callback de atualização com frequência crescente até ter sucesso e recuperar um token com uma validade suficientemente longa.

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

Se desejar cancelar tarefas de atualização agendadas, descarte o objeto Credential.

Atualização proativa de um token para um usuário do Teams

Para minimizar o número de viagens de ida e volta para a API de Identidade de Comunicação do Azure, verifique se o token do Microsoft Entra que você está passando para uma troca tem validade suficiente (> 10 minutos). Caso o MSAL retorne um token armazenado em cache com uma validade mais curta, você terá as seguintes opções para ignorar o cache:

  1. Atualizar o token à força
  2. Aumente a janela de renovação de tokens do MSAL para mais de 10 minutos

Opção 1: Iniciar o fluxo de aquisição de tokens com AuthenticationParameters.forceRefresh definido como 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;
        });
    }
}

Opção 2: Inicializar o contexto de autenticação MSAL instanciando um PublicClientApplication com um SystemOptions.tokenRenewalOffsetSeconds personalizado.

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

Cancelando a atualização

Para que os clientes de comunicação possam cancelar tarefas de atualização contínuas, é necessário passar um objeto de cancelamento para o retorno de chamada de atualização. Este padrão aplica-se apenas a JavaScript e .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...');
});

Se desejar cancelar tarefas de atualização subsequentes, descarte o objeto Credential.

Limpar os recursos

Como o objeto Credential pode ser passado para várias instâncias de cliente de Chat ou Calling, o SDK não faz suposições sobre seu tempo de vida e deixa a responsabilidade de seu descarte para o desenvolvedor. Cabe aos aplicativos dos Serviços de Comunicação descartar a instância de Credencial quando ela não for mais necessária. A eliminação da credencial também cancela as ações de atualização agendadas quando a atualização proativa está ativada.

Chame a função .dispose().

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

Manipulando uma saída

Dependendo do cenário, pode querer terminar a sessão de um utilizador em um ou mais serviços:

  • Para terminar a sessão de um utilizador num único serviço, descarte o objeto "Credential".
  • Para desassociar um utilizador de vários serviços, implemente um mecanismo de sinalização para notificar todos os serviços para descartar o objeto Credential e, além disso, revogar todos os tokens de acesso para uma identidade específica.

Próximos passos

Este artigo descreveu como:

  • Inicialize e descarte corretamente um objeto de credenciais
  • Implementar um callback de atualização de token
  • Otimize sua lógica de atualização de token