ボットにシングル サインオンを追加する

この記事の対象: SDK v4

この記事では、ボットでシングル サインオン (SSO) 機能を使用する方法について説明します。 これを行うために、この機能では、コンシューマー ボット (ルートまたはボットとも呼ばれます) を使用して、スキルまたはボットと対話します。 この記事では、条件ルート ボットとスキル ボットという用語を使用します。

SSO サポートを含める場合、ユーザーは ID プロバイダーを使用してルート ボットにサインインすることができ、コントロールがスキルに渡されるときに再度サインインする必要はありません。

ルート ボットとスキル ボットは別のボットであり、異なるサーバー上で実行されている可能性があり、それぞれに個別のメモリと状態が存在します。 (スキルの詳細については、「スキルの概要」と「スキルの実装」のページを参照してください。) ユーザー認証の詳細については、「Bot Framework 認証の基本」、「ユーザー認証」、および「ボットへの認証の追加」を参照してください。

重要

Web チャットで Azure AI Bot Service 認証を使用する場合、注意する必要がある重要なセキュリティの考慮事項がいくつかあります。 詳細については、REST 認証に関する記事の「セキュリティに関する考慮事項」セクションを参照してください。

前提条件

サンプルについて

この記事では、RootBotSkillBot という 2 つのボットを参照しています。 RootBotSkillBot にアクティビティを転送します。 それらによって、この "一般的な" スキルのシナリオをモデル化します。

  • "ルート" ボットで 1 つまたは複数のスキル ボットを呼び出します。
  • ルート ボットとスキル ボットの両方で、「ボットに認証を追加する」に記載されている基本認証を実装します。
  • ユーザーはルート ボットにログインします。
  • SSO により、既にルート ボットにログインしているため、ユーザーによる操作を再度必要とすることなく、スキル ボットへのログインが行われます。

Bot Framework での認証の処理方法の概要については、「ユーザー認証」を参照してください。 SSO の背景情報については、「シングル サインオン」を参照してください。

RootBot は SSO をサポートします。 ユーザーに代わって SkillBot との通信が行われるため、ユーザーが _SkillBot で再度認証される必要はありません。

サンプル内のプロジェクトごとに、次のものが必要です。

  1. Azure にボット リソースを登録するための Microsoft Entra ID アプリケーション。
  2. 認証を行う Microsoft Entra ID ID プロバイダー アプリケーション

    Note

    現時点では、Microsoft Entra ID ID プロバイダーのみがサポートされています。

Azure RootBot リソースの作成

  1. Azure portalRootBot 用の Azure ボット リソースを作成する 「Azure ボットリソースを作成する」で説明されている手順に従います。
  2. ボット登録のアプリ IDクライアント シークレットをコピーし、保存します。

RootBot の Microsoft Entra ID ID を作成する

Microsoft Entra ID は、OAuth 2.0 などの業界標準プロトコルを使用して、ユーザーを安全にサインインさせるアプリケーションをビルドすることができるクラウド ID サービスです。

  1. Microsoft Entra ID を使用してユーザーを認証する ID アプリケーションを RootBot 用に作成します。 「Microsoft Entra ID ID プロバイダーの作成」にある手順に従います。

  2. 左側のペインで [マニフェスト] を選択します。

  3. accessTokenAcceptedVersion を 2 に設定します。

  4. [保存] を選択します。

  5. 左側ウィンドウで、[API を公開する] を選択します。

  6. 右側ウィンドウで、[範囲の追加] を選択します。

  7. 右端にある [範囲の追加] セクションで、[保存して続行] を選択します。

  8. 表示されたウィンドウの [同意できるユーザー] で、[管理者とユーザー] を選択します。

  9. 残りの必要な情報を入力します。

  10. [スコープの追加] を選択します。

  11. スコープの値をコピーして保存します。

RootBot 用の OAuth の接続設定を作成する

  1. RootBot ボットの登録で Microsoft Entra ID 接続を作成し、「Microsoft Entra ID」に記載されている値と、次に示す値を入力します。

  2. [トークン交換 URL] は空のままにしておきます。

  3. [スコープ] ボックスに、前の手順で保存した RootBot スコープの値を入力します。

    Note

    [スコープ] には、ユーザーが最初にルート ボットにサインインしたときの URL が含まれますが、[トークン交換 URL] は空のままです。

    たとえば、ルート ボット appidrootAppId で、スキル ボット appidskillAppId であると仮定します。 ルート ボットの [スコープ] は、api://rootAppId/customScope のようになり、ユーザーのログインに使用されます。 このルート ボットの [スコープ]は、その後 SSO 中に api://skillAppId/customscope と交換されます。

  4. 接続の名前をコピーして保存します。

Azure SkillBot リソースを作成する

  1. Azure portalSkillBot 用の Azure ボット リソースを作成する 「Azure ボットリソースを作成する」で説明されている手順に従います。
  2. ボット登録のアプリ IDクライアント シークレットをコピーし、保存します。

SkillBot の Microsoft Entra ID ID を作成する

Microsoft Entra ID は、OAuth 2.0 などの業界標準プロトコルを使用して、ユーザーを安全にサインインさせるアプリケーションをビルドすることができるクラウド ID サービスです。

  1. SkillBot 用に、Microsoft Entra ID を使用してボットを認証する ID アプリケーションを作成します。 「Microsoft Entra ID ID プロバイダーの作成」にある手順に従います。

  2. 左側のペインで [マニフェスト] を選択します。

  3. accessTokenAcceptedVersion を 2 に設定します。

  4. [保存] を選択します。

  5. 左側ウィンドウで、[API を公開する] を選択します。

  6. 右側ウィンドウで、[範囲の追加] を選択します。

  7. 右端にある [範囲の追加] セクションで、[保存して続行] を選択します。

  8. 表示されたウィンドウの [同意できるユーザー] で、[管理者とユーザー] を選択します。

  9. 残りの必要な情報を入力します。

  10. [スコープの追加] を選択します。

  11. スコープの値をコピーして保存します。

  12. クライアント アプリケーションの追加を選択します。 右端のセクションの [クライアント ID] ボックスに、以前に保存したアプリ ID である RootBot ID を入力します。 必ず、登録アプリ ID ではなく RootBot ID を使用します。

    Note

    クライアント アプリケーションの場合、Azure AI Bot Service では、Microsoft Entra ID B2C ID プロバイダーでのシングル サインオンはサポートされていません。

  13. [Authorized scope]\(承認済みのスコープ\) で、スコープ値の横のチェック ボックスをオンにします。

  14. [アプリケーションの追加] をクリックします。

  15. 左側のナビゲーション ウィンドウで [API のアクセス許可] を選択します。 アプリの API アクセス許可を明示的に設定するのが成功事例です。

    1. 右側のウィンドウで、[アクセス許可の追加] をクリックします。

    2. [Microsoft API] を選択し、次に [Microsoft Graph] を選択します。

    3. [委任されたアクセス許可] を選択し、必要とするアクセス許可が選択されていることを確認します。 このサンプルには、以下に示すアクセス許可が必要です。

      Note

      [管理者の同意が必要] とマークされているアクセス許可については、ユーザーとテナント管理者の両方がログインする必要があります。

      • openid
      • profile
      • User.Read
      • User.ReadBasic.All
    4. [アクセス許可の追加] を選択します.

SkillBot 用の OAuth 接続設定を作成する

  1. SkillBot ボットの登録で Microsoft Entra ID 接続を作成し、「Microsoft Entra ID」に記載されている値と、次に示す値を入力します。

  2. [トークン交換 URL] ボックスに、前の手順で保存した SkillBot 範囲の値を入力します。

  3. [範囲] ボックスに、次の値を空白スペースで区切って入力します。profileUser.ReadUser.ReadBasic.All[範囲] ボックスに、次の値を空白スペースで区切って入力します。openid

  4. 接続の名前をコピーして、ファイルに保存します。

接続をテストする

  1. 接続入力を選択して、作成した接続を開きます。
  2. [サービス プロバイダー接続設定] ウィンドウの上部にある [テスト接続] を選択します。
  3. 初回は新しいブラウザー タブが開き、アプリが要求しているアクセス許可の一覧が表示され、承認を求められます。
  4. [Accept](承認) を選択します。
  5. これにより、[<your-connection-name> への接続テストに成功しました] ページにリダイレクトされます。

詳細については、「開発者向け Microsoft Entra ID (v1.0) の概要」と「Microsoft ID プラットフォーム (v2.0) の概要」を参照してください。 v1 と v2 エンドポイントの違いについては、「Microsoft ID プラットフォーム (v2.0) に更新する理由」を参照してください。 全情報については、「Microsoft ID プラットフォーム (旧「開発者向け Microsoft Entra ID」)」を参照してください。

サンプル コードを準備する

次に示すように、両方のサンプルの appsettings.json ファイルを更新する必要があります。

  1. GitHub リポジトリから、「SSO with Simple Skill Consumer and Skill」 (シンプルなスキル コンシューマーとスキルを使用した SSO) というサンプルをクローンします。

  2. SkillBot プロジェクトの appsettings.json ファイルを開きます。 保存したファイルから、次の値を割り当てます。

    {
        "MicrosoftAppId": "<SkillBot registration app ID>",
        "MicrosoftAppPassword": "<SkillBot registration password>",
        "ConnectionName": "<SkillBot connection name>",
        "AllowedCallers": [ "<RootBot registration app ID>" ]
    }
    
    
  3. RootBot プロジェクトの appsettings.json ファイルを開きます。 保存したファイルから、次の値を割り当てます。

    {
        "MicrosoftAppId": "<RootBot registration app ID>",
        "MicrosoftAppPassword": "<RootBot registration password>",
        "ConnectionName": "<RootBot connection name>",
        "SkillHostEndpoint": "http://localhost:3978/api/skills/",
        "BotFrameworkSkills": [
                {
                "Id": "SkillBot",
                "AppId": "<SkillBot registration app ID>",
                "SkillEndpoint": "http://localhost:39783/api/messages"
                }
            ]
    }
    

サンプルをテストする

テストには次のものを使用します。

  • RootBot コマンド

    • login では、ユーザーが RootBot を使用して Microsoft Entra ID 登録にサインインできるようにします。 サインインすると、SSO によって SkillBot へのサインインも自動的に行われます。 ユーザーはもう一度サインインする必要がありません。
    • token ではユーザーのトークンを表示します。
    • logout では、ユーザーを RootBot からログアウトします。
  • SkillBot コマンド

    • skill login では、ユーザーに代わって RootBot による SkillBot へのサインインができるようにします。 ユーザーが既にサインインしている場合、SSO に失敗しない限り、ユーザーにサインイン カードは表示されません。
    • skill token では、SkillBot からのユーザーのトークンを表示します。
    • skill logout では、ユーザーを SkillBot からログアウトします。

Note

ユーザーが初めてスキルで SSO を試すときには、ログインするための OAuth カードが表示されることがあります。 これは、スキルの Microsoft Entra ID アプリに対する同意がまだ付与されていないためです。 これを回避するために、Microsoft Entra ID アプリによって要求されたグラフのアクセス許可に対して管理者の同意を付与することができます。

Bot Framework Emulator をインストールします (まだインストールしていない場合)。 「エミュレーターを使用したデバッグ」もご覧ください。

ボットのサンプル ログインが機能するように、エミュレーターを構成する必要があります。 「エミュレーターを認証用に構成する」に示されているように、次の手順を使用します。

認証メカニズムを構成したら、実際にボット サンプル テストを実行できます。

  1. Visual Studio で、SSOWithSkills.sln ソリューションを開き、複数のプロセスのデバッグを開始するように構成します。

  2. ご自身のコンピューター上でローカルにデバッグを開始します。 RootBot プロジェクトの appsettings.json ファイル内に、次の設定があることに注意してください。

    "SkillHostEndpoint": "http://localhost:3978/api/skills/"
    "SkillEndpoint": "http://localhost:39783/api/messages"
    

    Note

    これらの設定は、RootBotSkillBot の両方がローカル コンピューター上で実行されていることを意味します。 エミュレーターと RootBot の通信はポート 3978 で、RootBotSkillBot の通信はポート 39783 で行われます。 デバッグを開始するとすぐに、2 つの既定のブラウザー ウィンドウが開きます。 1 つはポート 3978、もう 1 つはポート 39783 についてのものです。

  3. エミュレーターを起動します。

  4. ボットに接続するときに、RootBot の登録アプリ ID とパスワードを入力します。

  5. hi」と入力して会話を開始します。

  6. login」と入力します。 RootBot によって [AAD にサインイン] 認証カードが表示されます。

    サインイン カードの例。

  7. [サインイン] を選択します。 [Confirm Open URL]\(URL を開く確認\) ポップアップ ダイアログが表示されます。

    「URL を開く」確認メッセージのスクリーンショット。

  8. 確認 を選択します。 ログイン後、RootBot トークンが表示されます。

  9. token」と入力してトークンを再度表示します。

    ルート トークンを表示するメッセージの例。

    これで、SkillBot と通信する準備ができました。 RootBot を使用してサインインした後は、サインアウトするまで、認証情報を再度入力する必要はありません。これにより、SSO が正常に動作していることがわかります。

  10. エミュレーター ボックスに「skill login」と入力します。 もう一度ログインするように求められることはありません。 代わりに、SkillBot のトークンが表示されます。

  11. skill token」と入力してトークンを再度表示します。

  12. これで、「skill logout」と入力して SkillBot からサインアウトできます。 次に、「logout」と入力して SimpleRootBoot からサインアウトします。

追加情報

次のタイムシーケンス図は、この記事で使用されているサンプルに適用され、関連するさまざまなコンポーネント間のやりとりを示しています。 ABSAzure AI Bot Service を表します。

スキル トークンのフローを示すシーケンス図。

  1. 初回時、ユーザーは RootBot に対する login コマンドを入力します。
  2. ユーザーにサインインを求める OAuthCardRootBot から送信されます。
  3. ユーザーが認証情報を入力し、それが ABS (Azure AI Bot Service) に送信されます。
  4. ユーザーの資格情報に基づいて生成された認証トークンが、ABS から RootBot に送信されます。
  5. ユーザーに示されるルートのトークンが、RootBot に表示されます。
  6. ユーザーは SkillBot に対する skill login コマンドを入力します。
  7. SkillBot から RootBotOAuthCard が送信されます。
  8. RootBotABS交換可能なトークンが要求されます。
  9. SSO は、SkillBot スキル トークンRootBot に送信します。
  10. ユーザーに示されるスキルのトークンが、RootBot に表示されます。 ユーザーが SkillBot にサインインする必要なく、スキルのトークンが生成されたことに注意してください。 これは SSO によるものです。

次の例は、トークン交換がどのように行われるかを示しています。 このコードは TokenExchangeSkillHandler.cs ファイルからのコードです。

private async Task<bool> InterceptOAuthCards(ClaimsIdentity claimsIdentity, Activity activity)
{
    var oauthCardAttachment = activity.Attachments?.FirstOrDefault(a => a?.ContentType == OAuthCard.ContentType);
    if (oauthCardAttachment != null)
    {
        var targetSkill = GetCallingSkill(claimsIdentity);
        if (targetSkill != null)
        {
            var oauthCard = ((JObject)oauthCardAttachment.Content).ToObject<OAuthCard>();

            if (!string.IsNullOrWhiteSpace(oauthCard?.TokenExchangeResource?.Uri))
            {
                using (var context = new TurnContext(_adapter, activity))
                {
                    context.TurnState.Add<IIdentity>("BotIdentity", claimsIdentity);

                    // AAD token exchange
                    try
                    {
                        var result = await _tokenExchangeProvider.ExchangeTokenAsync(
                            context,
                            _connectionName,
                            activity.Recipient.Id,
                            new TokenExchangeRequest() { Uri = oauthCard.TokenExchangeResource.Uri }).ConfigureAwait(false);

                        if (!string.IsNullOrEmpty(result?.Token))
                        {
                            // If token above is null, then SSO has failed and hence we return false.
                            // If not, send an invoke to the skill with the token. 
                            return await SendTokenExchangeInvokeToSkill(activity, oauthCard.TokenExchangeResource.Id, result.Token, oauthCard.ConnectionName, targetSkill, default).ConfigureAwait(false);
                        }
                    }
                    catch
                    {
                        // Show oauth card if token exchange fails.
                        return false;
                    }

                    return false;
                }
            }
        }
    }
    return false;
}