次の方法で共有


資格情報のベスト プラクティス

この記事では、Azure Communication Services SDK で ユーザー アクセス トークン を管理するためのベスト プラクティスについて説明します。 このガイダンスに従って、アプリケーションで使用されるリソースを最適化し、Azure Communication Identity API へのラウンドトリップの数を減らします。

通信トークンの資格情報

通信トークン資格情報 (資格情報) は、ユーザー アクセス トークンをラップする認証プリミティブです。 これは、チャットや通話などの Communication Services のユーザーを認証するために使用されます。 さらに、開発者の利便性を高める組み込みのトークン更新機能も提供します。

セッションの有効期間の選択

シナリオによっては、アプリケーションに対して発行されるトークンの有効期間を調整できます。 次のベスト プラクティスまたはその組み合わせは、シナリオに最適なソリューションを実現するのに役立ちます。

カスタム トークンの有効期限の設定

新しいトークンを要求するときは、1 回限りのチャット メッセージまたは時間制限付きの通話セッションに有効期間の短いトークンを使用することをお勧めします。 アプリケーションを使用するエージェントには、有効期間が長いトークンを長期間使用することをお勧めします。 既定のトークンの有効期限は 24 時間です。 次のように、省略可能なパラメーターに 1 時間から 24 時間の値を指定することで、有効期限をカスタマイズできます。

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

静的トークン

有効期間の短いクライアントの場合は、静的トークンを使用して資格情報を初期化します。 この方法は、1 回限りのチャット メッセージの送信や時間制限付きの通話セッションなどのシナリオに適しています。

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: 通信ユーザーのトークンを更新する

名前で指定されたユーザーの新しい有効なトークンをフェッチできるように、 /getToken エンドポイントを使用して Express 上に構築された Node.js アプリケーションがあるとします。

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 ユーザーのトークンを更新する

/getTokenForTeamsUser エンドポイントを使用して Express 上に構築された Node.js アプリケーションがあるとします。これにより、Teams ユーザーの Microsoft Entra アクセス トークンを、有効期限が一致する新しいコミュニケーション ID アクセス トークンと交換できます。

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

次に、クライアント アプリケーションにトークン リフレッシャー コールバックを実装する必要があります。その役割は次のとおりです。

  1. Teams ユーザーの Microsoft Entra アクセス トークンを更新します。
  2. Teams ユーザーの Microsoft Entra アクセス トークンをコミュニケーション ID アクセス トークンと交換します。
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 Authentication Library (MSAL) を使用して Microsoft Entra アクセス トークンを更新します。 API を呼び出す Microsoft Entra トークンを取得するためのガイドに従って、まず、ユーザーの操作なしでトークンを取得しようとします。 それが不可能な場合は、対話型フローの 1 つをトリガーします。

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 Communication Identity API へのラウンドトリップの数を最小限に抑えるには、 交換 に渡す Microsoft Entra トークンの有効期間が十分に長い (> 10 分) ことを確認します。 MSAL が有効期間が短いキャッシュされたトークンを返す場合は、キャッシュをバイパスする次のオプションがあります。

  1. トークンを強制的に更新する
  2. MSAL のトークン更新期間を 10 分以上に増やす

オプション 1: AuthenticationParameters.forceRefreshtrue に設定してトークン取得フローをトリガーします。

// 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 は有効期間に関する前提を持たないため、開発者はその破棄の責任を負います。 資格情報インスタンスが不要になった場合は、Communication Services アプリケーションによって破棄されます。 また、プロアクティブ更新が有効になっている場合、資格情報を破棄すると、スケジュールされた更新アクションも取り消されます。

.dispose() 関数を呼び出します。

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

サインアウトの処理

シナリオによっては、1 つ以上のサービスからユーザーをサインアウトしたい場合があります。

  • 1 つのサービスからユーザーをサインアウトするには、Credential オブジェクトを 破棄 します。
  • 複数のサービスからユーザーをサインアウトするには、すべてのサービスに Credential オブジェクトを 破棄 するよう通知する通知メカニズムを実装し、さらに、特定の ID のすべてのアクセス トークンを取り消します

次のステップ

この記事では、次の方法について説明しました。

  • Credential オブジェクトを正しく初期化して破棄する
  • トークン リフレッシャー コールバックを実装する
  • トークン更新ロジックを最適化する