本文提供在 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,提供中止重新整理作業的方式,並將 AbortSignal 或 CancellationToken 傳遞至您的函式。 建議您接受這些物件、使用這些物件,或進一步傳遞這些物件。
範例 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 /getTokenForTeamsUser 上的 Node.js 應用程式,其端點允許使用相符的到期時間交換 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
});
如果要取消排程的重新整理工作,請處置認證物件。
主動重新整理 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()
處理登出
視您的案例而定,您可能想要從一或多個服務註銷使用者:
後續步驟
本文說明如何:
- 正確初始化和處置認證物件
- 實作權杖重新整理器回呼
- 最佳化權杖重新整理邏輯