本文提供在 Azure 通訊服務 SDK 中管理使用者存取令牌的最佳做法。 遵循此指引可協助您優化應用程式所使用的資源,並減少往返 Azure 通訊身分識別 API 的數目。
通訊令牌認證
通訊令牌認證 (Credential) 是包裝使用者存取令牌的驗證基本類型。 它用來驗證通訊服務中的使用者,例如聊天或通話。 此外,它也提供內建令牌重新整理功能,方便開發人員使用。
選擇工作階段存留期
視您的案例而定,您可能想要調整為應用程序發行之令牌的生命週期。 下列最佳做法或其組合可協助您為案例達成最佳解決方案:
- 自定義令牌到期時間 以符合您的特定需求。
- 使用一次性聊天訊息或限時通話會話的 靜態令牌 來初始化認證。
- 針對使用應用程式較長時間的代理程式使用回呼函式。
設定自定義令牌到期時間
要求新的令牌時,我們建議針對一次性聊天訊息使用簡短的存留期令牌,或針對使用應用程式較長時間的代理程式使用較長的通話會話,以及較長的存留期令牌。 默認令牌到期時間為24小時,但您可以藉由將一小時到24小時之間的值提供給選擇性參數來自定義它,如下所示:
const tokenOptions = { tokenExpiresInMinutes: 60 };
const user = { communicationUserId: userId };
const scopes = ["chat"];
let communicationIdentityToken = await identityClient.getToken(user, scopes, tokenOptions);
靜態令牌
若為短期用戶端,請使用靜態令牌初始化認證。 這種方法適用於傳送一次性聊天訊息或限時通話會話等案例。
let communicationIdentityToken = await identityClient.getToken({ communicationUserId: userId }, ["chat", "voip"]);
const tokenCredential = new AzureCommunicationTokenCredential(communicationIdentityToken.token);
回調函式
對於長時間存在的用戶端,請使用回呼函式初始化認證,以確保通訊期間持續驗證狀態。 例如,此方法適用於長時間通話會話。
const tokenCredential = new AzureCommunicationTokenCredential({
tokenRefresher: async (abortSignal) => fetchTokenFromMyServerForUser(abortSignal, "<user_name>")
});
令牌重新整理
若要正確實作令牌重新整理器回呼,程式代碼必須傳回具有有效 JSON Web 令牌 (JWT) 的字串。 傳回的令牌必須隨時有效(其到期日是在未來設定的)。 某些平臺,例如 JavaScript 和 .NET,提供中止重新整理作業的方式,並將 或 CancellationToken
傳遞AbortSignal
至您的函式。 建議您接受這些物件、利用這些物件或進一步傳遞這些物件。
範例 1:重新整理通訊使用者的令牌
假設我們在 Express 上建置Node.js應用程式, /getToken
其端點允許擷取名稱所指定使用者的新有效令牌。
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 });
});
接下來,我們需要在用戶端應用程式中實作令牌重新整理器回呼,以正確利用 AbortSignal
並傳回未包裝的 JWT 字串。
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;
}
};
範例 2:重新整理 Teams 使用者的令牌
假設我們有一個建置在 Express 上的Node.js應用程式,其 /getTokenForTeamsUser
端點允許將 Teams 使用者的 Microsoft Entra 存取令牌交換為新的通訊身分識別存取令牌,並具有相符的到期時間。
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 });
});
接下來,我們需要在用戶端應用程式中實作令牌重新整理器回呼,其責任會是:
- 重新整理 Teams 使用者的 Microsoft Entra 存取令牌
- 交換 Teams 使用者的 Microsoft Entra 存取令牌,以取得通訊身分識別存取令牌
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;
}
}
在此範例中,我們使用 Microsoft 驗證連結庫 (MSAL) 來重新整理 Microsoft Entra 存取令牌。 遵循取得 Microsoft Entra 令牌以呼叫 API 的指南,我們先嘗試取得令牌,而不需用戶互動。 如果不可能,我們會觸發其中一個互動式流程。
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;
}
提供初始令牌
若要進一步優化程序代碼,您可以在應用程式的啟動時擷取令牌,並將它直接傳遞至認證。 提供初始令牌將會略過重新整理器回呼函式的第一次呼叫,同時保留對它的所有後續呼叫。
const tokenCredential = new AzureCommunicationTokenCredential({
tokenRefresher: async () => fetchTokenFromMyServerForUser("<user_id>"),
token: "<initial_token>"
});
主動式令牌重新整理
使用主動式重新整理,在令牌的隨選擷取期間消除任何可能的延遲。 主動式重新整理會在其存留期結束時,重新整理背景中的令牌。 當令牌即將到期時,其有效性結束前 10 分鐘,認證就會開始嘗試擷取令牌。 它會以遞增的頻率觸發重新整理器回呼,直到成功並擷取具有足夠長有效性的令牌為止。
const tokenCredential = new AzureCommunicationTokenCredential({
tokenRefresher: async () => fetchTokenFromMyServerForUser("<user_id>"),
refreshProactively: true
});
如果您想要取消排程的重新整理工作, 請處置 Credential 物件。
主動重新整理Teams使用者的令牌
若要將往返 Azure 通訊識別 API 的數目降到最低,請確定您為交換傳遞的 Microsoft Entra 令牌具有足夠長的有效性(>10 分鐘)。 如果 MSAL 傳回具有較短有效性的快取令牌,您有下列選項可以略過快取:
- 強制重新整理令牌
- 將 MSAL 的令牌更新視窗增加到 10 分鐘以上
選項 1:觸發令牌擷取流程,並將 AuthenticationParameters.forceRefresh
設定為 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;
});
}
}
選項 2:使用自定義 SystemOptions.tokenRenewalOffsetSeconds
具現化 PublicClientApplication
來初始化 MSAL 驗證內容。
const publicClientApplication = new PublicClientApplication({
system: {
tokenRenewalOffsetSeconds: 900 // 15 minutes (by default 5 minutes)
});
取消重新整理
若要讓通訊客戶端能夠取消進行中的重新整理工作,必須將取消對象傳遞至重新整理器回呼。 請注意,此模式僅適用於 JavaScript 和 .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...');
});
如果您想要取消後續的重新整理工作, 請 處置 Credential 物件。
清除資源
由於 Credential 物件可以傳遞至多個聊天或通話用戶端實例,因此 SDK 不會對其存留期進行任何假設,並將其處置責任留給開發人員。 通訊服務應用程式會在不再需要認證實例時處置。 處置認證也會在啟用主動式重新整理時取消排程的重新整理動作。
呼叫 .dispose()
函式。
const tokenCredential = new AzureCommunicationTokenCredential("<token>");
// Use the credential for Calling or Chat
const chatClient = new ChatClient("<endpoint-url>", tokenCredential);
// ...
tokenCredential.dispose()
處理註銷
視您的案例而定,您可能想要從一或多個服務註銷使用者:
- 若要從單一服務註銷使用者, 請處置 Credential 物件。
- 若要從多個服務註銷使用者,請實作訊號機制,以通知所有服務 處置 Credential 物件,此外, 撤銷指定身分識別的所有存取令牌 。
下一步
在本文中,您已了解如何:
- 正確初始化和處置 Credential 物件
- 實作令牌重新整理器回呼
- 優化令牌重新整理邏輯
若要深入瞭解,您可能想要探索下列快速入門指南: