シングル サインオン (SSO) を有効にするコードを追加する前に、Microsoft Entra 管理センターでアプリとボット リソースを構成してください。
Microsoft Entra IDからアクセス トークンを取得するようにアプリのコードを構成する必要があります。 アクセス トークンは、ボット アプリに代わって発行されます。
注:
Microsoft Teams Toolkit を使用して Teams アプリをビルドした場合は、「ツールと SDK」モジュールの手順を使用してアプリの SSO を有効にすることができます。 詳細については、「 Teams アプリにシングル サインオンを追加する」を参照してください。 Teams Toolkit では、Visual Studio Code の JavaScript、TypeScript、および C# アプリの SSO がサポートされています。
このセクションでは、次の手順について説明します。
開発環境変数を更新する
Microsoft Entra IDでアプリのクライアント シークレットと OAuth 接続設定を構成しました。 これらの値を使用してコードを構成する必要があります。
開発環境変数を更新するには:
ボット アプリ プロジェクトを開きます。
プロジェクトの環境ファイル (
.env) を開きます。次の変数を更新します。
-
CLIENT_IDの場合は、Microsoft Entra IDからボット ID を更新します。 -
CLIENT_SECRETの場合は、クライアント シークレットを更新します。 -
CONNECTION_NAMEの場合は、Microsoft Entra IDで構成した OAuth 接続の名前を更新します。 -
TENANT_IDの場合は、テナント ID を更新します。
注:
ボットがパブリック クラウド、Microsoft Azure Government クラウド、Microsoft Azure 21Vianet によって運用されているかどうかに関係なく、データ所在地の要件に基づいて、ボットと ID プロバイダーの OAuth リダイレクト URL をカスタマイズできます。 OAuth URL とデータ所在地の一覧については、「Azure AI Bot Serviceでの OAuth URL のサポート」を参照してください。
-
ファイルを保存します。
これで、ボット アプリと SSO に必要な環境変数が構成されました。 次に、OAuth を使用してアプリを初期化します。
OAuth を使用してアプリを初期化する
Teams SDK は、サーバーのライフサイクル、認証、トークン交換を内部的に処理する単一の App クラスを使用して、アプリの初期化を簡略化します。
using Microsoft.Teams.Apps.Extensions;
using Microsoft.Teams.Plugins.AspNetCore.Extensions;
var builder = WebApplication.CreateBuilder(args);
var connectionName = builder.Configuration["CONNECTION_NAME"]
?? throw new InvalidOperationException("Missing required configuration value: CONNECTION_NAME");
var appBuilder = App.Builder()
.AddOAuth(connectionName);
builder.AddTeams(appBuilder);
var app = builder.Build();
var teams = app.UseTeams();
注:
App クラスは、すべてのアダプター構成、ミドルウェア、エラー処理、およびサーバーのセットアップを内部的に処理します。
アクセス トークンを取得するための同意ダイアログ
ユーザーは、アクセス トークンを取得するためにボット アプリによって要求されたアクセス許可に同意する必要があります。 同意ダイアログは、アプリのスコープに基づいて表示されます。
1 対 1 のチャット
アプリ ユーザーが初めてアプリケーションを使用していて、ユーザーの同意が必要な場合は、次のダイアログ ボックスが表示されます。
ユーザーが [続行] を選択すると、次のいずれかのイベントが発生します。
ボット UI にサインイン ボタンがある場合は、ボットのサインイン フローがアクティブになります。 アプリ ユーザーの同意を必要とするアクセス許可を決定できます。 アプリに
openid以外の Graph アクセス許可が必要な場合は、この方法を使用します。OAuth カードにボットにサインイン ボタンがない場合は、最小限のアクセス許可セットにアプリ ユーザーの同意が必要です。 このトークンは、基本認証やアプリ ユーザーのメール アドレスの取得に役立ちます。
表示される同意ダイアログは、Microsoft Entra IDで定義されている open-id スコープ用です。 アプリ ユーザーは同意を 1 回だけ行う必要があります。 同意した後、アプリ ユーザーは、付与されたアクセス許可とスコープに対してボット アプリにアクセスして使用できます。
グループ チャット
グループ スコープでの認証の 2 つのシナリオを次に示します。
同意が必要Microsoft Entra ID
ボットが初めてグループ チャットに追加され、特定のユーザーに同意が必要な場合は、ボットを @mentions したユーザーにのみ同意ダイアログ ボックスが表示されます。 ユーザーは、ボット アプリによって要求されたアクセス許可に対して 1 回限りの同意を与えてアクセス トークンを取得する必要があります。
ユーザーはボットを @mentions します。 アダプティブ カードは、ユーザーの同意を要求するように表示されます。
同意Microsoft Entra ID必要ありません
ユーザーのアクセス許可が既定で付与されている場合、または信頼されたアプリに対して付与されている場合、ボットを @mentions ユーザーは、同意を与えることなくボットと直接対話できます。
注:
アプリ ユーザーが同意した後、他のアクセス許可に対してもう一度同意する必要はありません。 スコープで定義されているアクセス許可Microsoft Entra変更された場合、アプリ ユーザーはもう一度同意する必要がある場合があります。 ただし、同意プロンプトでアプリ ユーザーがアクセスできない場合、ボット アプリはサインインカードにフォールバックします。
重要
同意ダイアログが必要ないシナリオ:
- 管理者がテナントの代わりに同意を許可する場合、アプリ ユーザーに同意を求めるメッセージをまったく表示する必要はありません。 つまり、アプリ ユーザーは同意ダイアログを表示せず、アプリにシームレスにアクセスできます。
- Microsoft Entra アプリが Teams で認証を要求しているのと同じテナントに登録されている場合、アプリ ユーザーは同意を求められず、すぐにアクセス トークンが付与されます。 アプリ ユーザーは、Microsoft Entra アプリが別のテナントに登録されている場合にのみ、これらのアクセス許可に同意します。
エラーが発生した場合は、「 Teams での SSO 認証のトラブルシューティング」を参照してください。
サインインとトークンの受信を処理する
Teams SDK では、認証に単純なイベント ドリブン ハンドラーが使用されます。 IsSignedInを使用して認証状態をチェックし、 SSO フローをトリガーし、 イベントをサブスクライブして認証の成功を処理します。
teams.OnMessage(async (context, cancellationToken) =>
{
if (!context.IsSignedIn)
{
await context.SignIn(cancellationToken);
return;
}
var token = context.UserToken;
await context.Send($"You are signed in. Token length: {token?.Length}", cancellationToken);
});
teams.OnSignIn(async (_, teamsEvent, cancellationToken) =>
{
var context = teamsEvent.Context;
await context.Send("Successfully signed in! You can now use the bot.", cancellationToken);
});
注:
SDK は、トークン交換と検証を内部的に処理します。
OAuthPrompt、WaterfallDialog、または MainDialog クラスを手動で管理する必要がなくなりました。
サインインエラーを処理する
SSO を使用する場合、トークン交換が失敗した場合、Teams は signin/failure 呼び出しアクティビティをアプリに送信します。 SDK には、アクション可能なトラブルシューティング ガイダンスを使用して警告をログに記録する既定のハンドラーが組み込まれています。 必要に応じて、独自のハンドラーを登録して、動作をカスタマイズできます。
teams.OnSignInFailure(async (context, cancellationToken) =>
{
var failure = context.Activity.Value;
Console.WriteLine($"Sign-in failed: {failure?.Code} - {failure?.Message}");
await context.Send("Sign-in failed. Please try again.", cancellationToken);
});
アプリ ユーザーのサインアウトを処理する
signout メソッドを呼び出して、ユーザー トークン サービス キャッシュからユーザーの認証トークンを削除し、効果的にサインアウトします。Teams SDK は、DialogContext、UserTokenClient、CancelAllDialogsAsyncを使用する前のパターンを単純なメソッド呼び出しに置き換えます。
teams.OnMessage("/signout", async (context, cancellationToken) =>
{
if (!context.IsSignedIn)
{
await context.Send("You are not signed in.", cancellationToken);
return;
}
await context.SignOut(cancellationToken);
await context.Send("You have been signed out.", cancellationToken);
});
コード サンプル
| サンプルの名前 | 説明 | .NET | Node.js |
|---|---|---|---|
| ボット会話 SSO クイック スタート | SSO を使用して Teams ボットを迅速に設定し、1 対 1 およびグループ チャットのシームレスなユーザー認証を実現します。 | 表示 | 表示 |
注:
OnTeamsMessagingExtensionQueryAsyncおよび TeamsMessagingExtensionsSearchAuthConfigBot.cs ファイルからのOnTeamsAppBasedLinkQueryAsyncは、サポートされている唯一の SSO ハンドラーです。 その他の SSO ハンドラーはサポートされていません。
このセクションでは、次の手順について説明します。
- 開発環境変数を更新する
- トークンを要求するコードを追加する
- トークンを受け取るコードを追加する
- Bot Framework トークン ストアにトークンを追加する
- アプリ ユーザーのログアウトを処理する
開発環境変数を更新する
Microsoft Entra IDでアプリのクライアント シークレットと OAuth 接続設定を構成しました。 これらの変数を使用してアプリ コードを構成する必要があります。
開発環境変数を更新するには:
アプリ プロジェクトを開きます。
プロジェクトの
./envファイルを開きます。次の変数を更新します。
-
MicrosoftAppIdの場合は、Microsoft Entra IDからボット登録 ID を更新します。 -
MicrosoftAppPassword、ボット登録クライアント シークレットを更新します。 -
ConnectionNameの場合は、Microsoft Entra IDで構成した OAuth 接続の名前を更新します。 -
MicrosoftAppTenantIdの場合は、テナント ID を更新します。
-
ファイルを保存します。
これで、ボット アプリと SSO に必要な環境変数が構成されました。 次に、トークンを処理するためのコードを追加します。
トークンを要求するコードを追加する
トークンを取得する要求は、既存のメッセージ スキーマを使用した POST メッセージ要求です。 それは、OAuthCard の添付ファイルに含まれています。 OAuthCard クラスのスキーマは、 Microsoft Bot Schema 4.0 で定義されています。
TokenExchangeResource プロパティがカードに設定されている場合、Teams はトークンを更新します。 Microsoft Teams チャンネルの場合、トークンの要求を一意に認識するプロパティ Id だけが引き受けられます。
注:
Microsoft Bot Framework OAuthPrompt または MultiProviderAuthDialog は、SSO 認証でサポートされています。
アプリのコードを更新するには:
TeamsSSOTokenExchangeMiddlewareのコード スニペットを追加します。次のコード スニペットを
AdapterWithErrorHandler.cs(またはアプリのコード内の同等のクラス) に追加します。base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"]));注:
ユーザーが複数のアクティブなエンドポイントを持っている場合、与えられた要求に対して複数の応答を受け取る場合があります。 トークンを使用して、重複または冗長な応答をすべて排除する必要があります。 signin/tokenExchange の詳細については、「 TeamsSSOTokenExchangeMiddleware クラス」を参照してください。
トークンを要求するには、次のコード スニペットを使用します。
AdapterWithErrorHandler.csを追加した後、次のコードが表示される必要があります。public class AdapterWithErrorHandler : CloudAdapter { public AdapterWithErrorHandler( IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger<IBotFrameworkHttpAdapter> logger, IStorage storage, ConversationState conversationState) : base(configuration, httpClientFactory, logger) { base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"])); OnTurnError = async (turnContext, exception) => { // Log any leaked exception from the application. // NOTE: In production environment, you must consider logging this to // Azure Application Insights. Visit https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-telemetry?view=azure-bot-service-4.0&tabs=csharp to see how // to add telemetry capture to your bot. logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); // Send a message to the user. await turnContext.SendActivityAsync("The bot encountered an error or bug."); await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); if (conversationState != null) { try { // Delete the conversationState for the current conversation to prevent the // bot from getting stuck in an error-loop caused by being in a bad state. // ConversationState must be thought of as similar to "cookie-state" in a Web pages. await conversationState.DeleteAsync(turnContext); } catch (Exception e) { logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); } } // Send a trace activity, which will be displayed in the Bot Framework Emulator. await turnContext.TraceActivityAsync( "OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); }; } }
アクセス トークンを取得するための同意ダイアログ
アプリ ユーザーが初めてアプリを使用する場合は、SSO 認証に同意する必要があります。
アプリ ユーザーがユーザー名を選択すると、アクセス許可が付与され、アプリを使用できます。
表示される同意ダイアログは、Microsoft Entra IDで定義されている open-id スコープ用です。 アプリ ユーザーは同意を 1 回だけ行う必要があります。 同意した後、アプリ ユーザーは、付与されたアクセス許可とスコープに対してメッセージ拡張機能アプリにアクセスして使用できます。
重要
同意ダイアログが不要なシナリオ:
- 管理者がテナントに代わって同意を許可した場合、アプリ ユーザーに同意を求めるメッセージをまったく表示する必要はありません。 つまり、アプリ ユーザーは同意ダイアログを表示せず、アプリにシームレスにアクセスできます。
エラーが発生した場合は、「 Teams での SSO 認証のトラブルシューティング」を参照してください。
トークンを受け取るコードを追加する
トークンを使用した応答は、ボットが今日受け取る他の呼び出しアクティビティと同じスキーマを持つ呼び出しアクティビティを介して送信されます。 唯一の違いは、呼び出し名、サインイン/tokenExchange、および 値 フィールドです。 値フィールドには、ID、トークンを取得するための最初の要求の文字列、トークン フィールド、トークンを含む文字列値が含まれます。
応答を呼び出すには、次のコード スニペットの例を使用します。
public MainDialog(IConfiguration configuration, ILogger<MainDialog> logger)
: base(nameof(MainDialog), configuration["ConnectionName"])
{
AddDialog(new OAuthPrompt(
nameof(OAuthPrompt),
new OAuthPromptSettings
{
ConnectionName = ConnectionName,
Text = "Please Sign In",
Title = "Sign In",
Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5)
EndOnInvalidMessage = true
}));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
PromptStepAsync,
LoginStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken);
}
private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var tokenResponse = (TokenResponse)stepContext.Result;
if (tokenResponse?.Token != null)
{
var token = tokenResponse.Token;
// On successful login, the token contains sign in token.
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Login was not successful please try again."), cancellationToken);
}
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
注:
コード スニペットでは、ウォーターフォール ダイアログ ボットを使用します。 ウォーターフォール ダイアログの詳細については、「 コンポーネントダイアログとウォーターフォール ダイアログについて」を参照してください。
SSO を有効にするシナリオに応じて、 OnTeamsMessagingExtensionQueryAsync ハンドラーの turnContext.Activity.Value ペイロードまたは OnTeamsAppBasedLinkQueryAsyncでトークンを受け取ります。
JObject valueObject=JObject.FromObject(turnContext.Activity.Value);
if(valueObject["authentication"] !=null)
{
JObject authenticationObject=JObject.FromObject(valueObject["authentication"]);
if(authenticationObject["token"] !=null)
}
アクセス トークンを検証する
サーバー上の Web API は、アクセス トークンをデコードし、クライアントから送信されているかどうかを確認する必要があります。
注:
Bot Framework を使用すると、アクセス トークンの検証が処理されます。 Bot Framework を使用しない場合は、このセクションのガイドラインに従ってください。
アクセス トークンの検証の詳細については、「 トークンの検証」を参照してください。
JWT の検証を処理できるライブラリが複数入手可能です。 基本的な検証には、次のものが含まれます。
- トークンが整形式であることを確認します。
- トークンが目的の機関によって発行されたことを確認します。
- トークンが Web API の対象であることを確認します。
トークンの検証時には、次のガイドラインに注意してください。
- 有効な SSO トークンは、Microsoft Entra IDによって発行されます。 トークン内の
iss要求は、この値で始まる必要があります。 - トークンの
aud1パラメーターは、アプリの登録時に生成されたアプリ ID Microsoft Entra設定されます。 - トークンの
scpパラメーターは、access_as_userに設定されます。
アクセス トークンの例
次のコード スニペットは、アクセス トークンの一般的なデコードされたペイロードです。
{
aud: "2c3caa80-93f9-425e-8b85-0745f50c0d24",
iss: "https://login.microsoftonline.com/fec4f964-8bc9-4fac-b972-1c1da35adbcd/v2.0",
iat: 1521143967,
nbf: 1521143967,
exp: 1521147867,
aio: "ATQAy/8GAAAA0agfnU4DTJUlEqGLisMtBk5q6z+6DB+sgiRjB/Ni73q83y0B86yBHU/WFJnlMQJ8",
azp: "e4590ed6-62b3-5102-beff-bad2292ab01c",
azpacr: "0",
e_exp: 262800,
name: "Mila Nikolova",
oid: "6467882c-fdfd-4354-a1ed-4e13f064be25",
preferred_username: "milan@contoso.com",
scp: "access_as_user",
sub: "XkjgWjdmaZ-_xDmhgN1BMP2vL2YOfeVxfPT_o8GRWaw",
tid: "fec4f964-8bc9-4fac-b972-1c1da35adbcd",
uti: "MICAQyhrH02ov54bCtIDAA",
ver: "2.0"
}
Bot Framework トークン ストアにトークンを追加する
OAuth 接続を使用している場合は、Bot Framework トークン ストアでトークンを更新または追加する必要があります。 ストア内のトークンを更新または追加するために、次のコード スニペットの例を TeamsMessagingExtensionsSearchAuthConfigBot.cs (またはアプリのコード内の同等のファイル) に追加します。
注:
サンプル TeamsMessagingExtensionsSearchAuthConfigBot.cs は 、Tab、Bot、Message Extension (ME) SSO にあります。
protected override async Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
JObject valueObject = JObject.FromObject(turnContext.Activity.Value);
if (valueObject["authentication"] != null)
{
JObject authenticationObject = JObject.FromObject(valueObject["authentication"]);
if (authenticationObject["token"] != null)
{
//If the token is NOT exchangeable, then return 412 to require user consent.
if (await TokenIsExchangeable(turnContext, cancellationToken))
{
return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
}
else
{
var response = new InvokeResponse();
response.Status = 412;
return response;
}
}
}
return await base.OnInvokeActivityAsync(turnContext, cancellationToken).ConfigureAwait(false);
}
private async Task<bool> TokenIsExchangeable(ITurnContext turnContext, CancellationToken cancellationToken)
{
TokenResponse tokenExchangeResponse = null;
try
{
JObject valueObject = JObject.FromObject(turnContext.Activity.Value);
var tokenExchangeRequest =
((JObject)valueObject["authentication"])?.ToObject<TokenExchangeInvokeRequest>();
var userTokenClient = turnContext.TurnState.Get<UserTokenClient>();
tokenExchangeResponse = await userTokenClient.ExchangeTokenAsync(
turnContext.Activity.From.Id,
_connectionName,
turnContext.Activity.ChannelId,
new TokenExchangeRequest
{
Token = tokenExchangeRequest.Token,
},
cancellationToken).ConfigureAwait(false);
}
#pragma warning disable CA1031 //Do not catch general exception types (ignoring, see comment below)
catch
#pragma warning restore CA1031 //Do not catch general exception types
{
//ignore exceptions.
//if token exchange failed for any reason, tokenExchangeResponse above remains null, and a failure invoke response is sent to the caller.
//This ensures the caller knows that the invoke has failed.
}
if (tokenExchangeResponse == null || string.IsNullOrEmpty(tokenExchangeResponse.Token))
{
return false;
}
return true;
}
アプリ ユーザーのログアウトを処理する
アプリ ユーザーがログアウトした場合にアクセス トークンを処理するには、次のコード スニペットを使用します。
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc,
CancellationToken cancellationToken = default(CancellationToken))
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
// Allow logout anywhere in the command.
if (text.IndexOf("logout") >= 0)
{
// The UserTokenClient encapsulates the authentication processes.
var userTokenClient = innerDc.Context.TurnState.Get<UserTokenClient>();
await userTokenClient.SignOutUserAsync(
innerDc.Context.Activity.From.Id,
ConnectionName,
innerDc.Context.Activity.ChannelId,
cancellationToken
).ConfigureAwait(false);
await innerDc.Context.SendActivityAsync(MessageFactory.Text("You have been signed out."), cancellationToken);
return await innerDc.CancelAllDialogsAsync(cancellationToken);
}
}
return null;
}
コード サンプル
このセクションでは、ボット認証 v3 SDK のサンプルを提供します。
| サンプルの名前 | 説明 | .NET | Node.js | Python | マニフェスト |
|---|---|---|---|---|---|
| ボット認証 | このサンプル アプリは、ボットが Teams 認証を使用する方法を示しています。 | 表示 | 表示 | 表示 | 該当なし |
| タブ、ボット、メッセージ拡張機能 (ME) SSO | このサンプル アプリでは、セキュリティで保護された認証に C# とMicrosoft Entra IDを使用して、Tab、Bot、Messaging Extension の Teams SSO 統合を示します。 | 表示 | 表示 | 該当なし | 表示 |
| タブ、ボット、メッセージ拡張機能 | このサンプルでは、Microsoft Teamsのボット、タブ、メッセージング拡張機能全体のMicrosoft Entra ID認証とFacebook認証について説明します。 | 表示 | 表示 | 該当なし | 表示 |
次の手順
Platform Docs