メッセージ拡張機能アプリで SSO を有効にするコードを追加します

シングル サインオン (SSO) を有効にするコードを追加する前に、必ず Microsoft Entra 管理センターでアプリとボット リソースを構成してください。

注:

OnTeamsMessagingExtensionQueryAsync TeamsMessagingExtensionsSearchAuthConfigBot.cs ファイルの SSO ハンドラーは、 ファイルからのOnTeamsAppBasedLinkQueryAsyncのみがサポートされます。 その他の SSO ハンドラーはサポートされません。

この記事の内容:

  1. 開発環境変数を更新する
  2. トークンを要求するコードを追加します
  3. トークンを受け取るコードを追加します
  4. Bot Framework トークン ストアにトークンを追加する
  5. アプリ ユーザーのログアウトを処理する

開発環境変数を更新する

Microsoft Entra ID でアプリのクライアント シークレットと OAuth 接続設定を構成しました。 これらの変数を使用してアプリ コードを構成する必要があります。

開発環境変数を更新するには:

  1. アプリ プロジェクトを開きます。

  2. プロジェクトの ./env ファイルを開きます。

  3. 次の変数を更新します。

    • MicrosoftAppIdの場合は、ボット登録 ID を Microsoft Entra ID から更新します。
    • MicrosoftAppPasswordについては、ボット登録のクライアント シークレットを更新します。
    • ConnectionNameについては、Microsoft Entra ID で構成した OAuth 接続の名前を更新します。
    • MicrosoftAppTenantIdについては、テナント ID を更新します。
  4. ファイルを保存します。

これで、ボット アプリと SSO に必要な環境変数が構成されました。 次に、トークンを処理するためのコードを追加します。

トークンを要求するコードを追加します

トークンを取得する要求は、既存のメッセージ スキーマを使用した POST メッセージ要求です。 それは、OAuthCard の添付ファイルに含まれています。 OAuthCard クラスのスキーマは、 Microsoft Bot Schema 4.0 で定義されています。 カード に TokenExchangeResource プロパティが設定されている場合、Teams はトークンを更新します。 Microsoft Teams チャンネルの場合、トークンの要求を一意に認識するプロパティ Id だけが引き受けられます。

注:

Microsoft Bot Framework OAuthPrompt または MultiProviderAuthDialog は、SSO 認証でサポートされています。

アプリのコードを更新するには:

  1. TeamsSSOTokenExchangeMiddleware 用のコード スニペットを追加します。

    次のコード スニペットを AdapterWithErrorHandler.cs (またはアプリのコード内の同等のクラス) に追加します。

    base.Use(new TeamsSSOTokenExchangeMiddleware(storage, configuration["ConnectionName"]));
    

    注:

    ユーザーが複数のアクティブなエンドポイントを持っている場合、与えられた要求に対して複数の応答を受け取る場合があります。 トークンを使用して、重複または冗長な応答をすべて排除する必要があります。 signin/tokenExchange の詳細については、「 TeamsSSOTokenExchangeMiddleware クラス」を参照してください。

  2. トークンを要求するには、次のコード スニペットを使用します。

    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 認証に同意する必要があります。

メッセージ拡張機能アプリの SSO 認証

アプリ ユーザーがユーザー名を選択すると、アクセス許可が付与され、アプリを使用できます。

メッセージ拡張機能アプリの SSO 認証が完了しました

表示される同意ダイアログは、Microsoft Entra ID で定義されたオープン 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);
}

注:

コード スニペットでは、ウォーターフォール ダイアログ ボットを使用します。 ウォーターフォール ダイアログの詳細については、「 コンポーネントとウォーターフォール ダイアログについて」を参照してください。

トークン OnTeamsMessagingExtensionQueryAsync SSO を有効にするシナリオに応じて、 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)
    {
        // Process the token.
    }
}

アクセス トークンを検証する

サーバー上の 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 (またはアプリのコード内の同等のファイル) に追加します。

注:

タブ、ボット、およびメッセージ拡張機能 (ME) SSO にあるサンプル TeamsMessagingExtensionsSearchAuthConfigBot.csを見つけることができます。

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

            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 fails for any reason,
        // tokenExchangeResponse remains null and a failure invoke response is sent.
    }

    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 認証を使用する方法を示します。 表示 表示 表示 該当なし
タブ、ボット、メッセージ拡張機能の SSO このサンプル アプリでは、安全な認証のために C# と Microsoft Entra ID を使用したタブ、ボット、メッセージ拡張機能の Teams SSO 統合を示します。 表示 表示 該当なし 表示
タブ、ボット、メッセージ拡張機能 このサンプルでは、Microsoft Teams のボット、タブ、メッセージ拡張機能全体での Microsoft Entra ID と Facebook 認証について説明します。 表示 表示 該当なし 表示

次の手順