練習 - 新增單一登錄

已完成

在此練習中,您會將單一登錄新增至訊息延伸模組,以驗證用戶查詢。

以搜尋為基礎的訊息延伸模組中驗證挑戰的螢幕快照。隨即顯示登入的連結。

設定後端 API 應用程式註冊

首先,建立後端 API 的 Microsoft Entra 應用程式註冊。 基於此練習的目的,您會建立新的應用程式,不過,在生產環境中,您會使用現有的應用程式註冊。

在瀏覽器視窗中:

  1. 流覽至 Azure 入口網站
  2. 開啟入口網站功能表,然後選 取 [Microsoft標識符]
  3. 選取 [應用程式註冊] ,然後選取 [ 新增註冊]
  4. 在 [註冊應用程式] 表單中,指定下列值:
    1. 名稱:產品 API
    2. 支持帳戶類型:任何組織目錄中的帳戶 (任何Microsoft內部標識符租使用者 - 多租使用者)
  5. 選取 [註冊 ] 以建立應用程式註冊
  6. 在應用程式註冊左側功能表中,選取 [ 公開 API]
  7. 取 [新增儲存 ] 以建立新的應用程式識別碼 URI
  8. 在 [此 API 定義的範圍] 區段中,選取 [ 新增範圍]
  9. 在 [新增範圍] 表單中,指定下列值:
    1. 範圍名稱: Product.Read
    2. 誰可以同意?:系統管理員和使用者
    3. 管理員同意顯示名稱:讀取產品
    4. 管理員同意描述:允許應用程式讀取產品數據
    5. 使用者同意顯示名稱:讀取產品
    6. 使用者同意描述:允許應用程式讀取產品數據
    7. 狀態: 已啟用
  10. 取 [新增範圍 ] 以建立範圍

接下來,記下應用程式註冊標識碼和範圍標識符。 您需要這些值來設定用來取得後端 API 存取權杖的應用程式註冊。

  1. 在應用程式註冊左側功能表中,選取 [ 指令清單]
  2. 複製 appId 屬性值並加以儲存以供稍後使用
  3. 複製 api.oauth2PermissionScopes[0].id 屬性值,並儲存以供稍後使用

由於我們需要專案中的這些值,請將它們新增至環境檔案。

在 Visual Studio 和 TeamsApp 專案中:

  1. env 資料夾中,開啟 .env.local

  2. 在檔案中,建立下列環境變數,並將值設定為應用程式註冊標識碼和範圍標識碼:

    BACKEND_API_ENTRA_APP_ID=<app-registration-id>
    BACKEND_API_ENTRA_APP_SCOPE_ID=<scope-id>
    
  3. Save your changes

建立應用程式註冊指令清單檔案以向後端 API 進行驗證

若要向後端 API 進行驗證,您需要應用程式註冊才能取得用來呼叫 API 的存取令牌。

接下來,建立應用程式註冊指令清單檔案。 指令清單會定義應用程式註冊的 API 許可權範圍和重新導向 URI。

在 Visual Studio 和 TeamsApp 專案中:

  1. infra\entra 資料夾中,建立名為 entra.products.api.manifest.json

  2. 在 檔案中,新增下列程序代碼:

    {
      "id": "${{PRODUCTS_API_ENTRA_APP_OBJECT_ID}}",
      "appId": "${{PRODUCTS_API_ENTRA_APP_ID}}",
      "name": "${{APP_INTERNAL_NAME}}-product-api-${{TEAMSFX_ENV}}",
      "accessTokenAcceptedVersion": 2,
      "signInAudience": "AzureADMultipleOrgs",
      "optionalClaims": {
        "idToken": [],
        "accessToken": [
          {
            "name": "idtyp",
            "source": null,
            "essential": false,
            "additionalProperties": []
          }
        ],
        "saml2Token": []
      },
      "requiredResourceAccess": [
        {
          "resourceAppId": "${{BACKEND_API_ENTRA_APP_ID}}",
          "resourceAccess": [
            {
              "id": "${{BACKEND_API_ENTRA_APP_SCOPE_ID}}",
              "type": "Scope"
            }
          ]
        }
      ],
      "oauth2Permissions": [],
      "preAuthorizedApplications": [],
      "identifierUris": [],
      "replyUrlsWithType": [
        {
          "url": "https://token.botframework.com/.auth/web/redirect",
          "type": "Web"
        }
      ]
    }
    
  3. Save your changes

requiredResourceAccess 屬性會指定應用程式註冊標識碼和後端 API 的範圍標識碼。

replyUrlsWithType 屬性會指定 Bot Framework 令牌服務在使用者驗證之後,用來將存取令牌傳回令牌服務的重新導向 URI。

接下來,更新自動化工作流程以建立和更新應用程式註冊。

在 TeamsApp 專案中:

  1. 啟teamsapp.local.yml

  2. 在 檔案中,尋找使用 addApp/update 動作的步驟

  3. 在動作之後,新增 aadApp/createaadApp/update 動作,以建立和更新應用程式註冊:

      - uses: aadApp/create
        with:
            name: ${{APP_INTERNAL_NAME}}-products-api-${{TEAMSFX_ENV}}
            generateClientSecret: true
            signInAudience: AzureADMultipleOrgs
        writeToEnvironmentFile:
            clientId: PRODUCTS_API_ENTRA_APP_ID
            clientSecret: SECRET_PRODUCTS_API_ENTRA_APP_CLIENT_SECRET
            objectId: PRODUCTS_API_ENTRA_APP_OBJECT_ID
            tenantId: PRODUCTS_API_ENTRA_APP_TENANT_ID
            authority: PRODUCTS_API_ENTRA_APP_OAUTH_AUTHORITY
            authorityHost: PRODUCTS_API_ENTRA_APP_OAUTH_AUTHORITY_HOST
    
      - uses: aadApp/update
        with:
            manifestPath: "./infra/entra/entra.products.api.manifest.json"
            outputFilePath : "./infra/entra/build/entra.products.api.${{TEAMSFX_ENV}}.json"
    
  4. Save your changes

aadApp/create 動作會建立具有指定名稱、物件的新應用程式註冊,併產生客戶端密碼。 writeToEnvironmentFile 屬性會將應用程式註冊標識碼、用戶端密碼、對象標識碼、租使用者標識碼、授權單位和授權單位主機寫入環境檔案。 客戶端密碼會加密並安全地儲存在 env.local.user 檔案中。 用戶端密碼的環境變數名稱前面會加上 SECRET_,它會告知Teams工具組不要在記錄中寫入值。

aadApp/update 動作會以指定的指令清單檔案更新應用程式註冊。

集中連線設定名稱

首先,將環境檔案中的連線設定名稱集中化,並更新應用程式組態,以在運行時間存取環境變數值。

在 Visual Studio 和 TeamsApp 項目中繼續:

  1. env 資料夾中,開啟 .env.local

  2. 在 檔案中,新增下列程序代碼:

    CONNECTION_NAME=ProductsAPI
    
  3. 啟teamsapp.local.yml

  4. 在 檔案中,尋找使用以 ./appsettings 為目標 之 file/createOrUpdateJsonFile 動作 的步驟。Development.json 檔案。 更新內容陣列以包含 CONNECTION_NAME 環境變數,並將值寫入 appsettings。Development.json 檔案:

      - uses: file/createOrUpdateJsonFile
        with:
          target: ../ProductsPlugin/appsettings.Development.json
          content:
            BOT_ID: ${{BOT_ID}}
            BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
            CONNECTION_NAME: ${{CONNECTION_NAME}}
    
  5. Save your changes

接下來,更新應用程式組態以存取 CONNECTION_NAME 環境變數。

在 ProductsPlugin 專案中:

  1. 啟Config.cs

  2. ConfigOptions 類別中,新增名為 CONNECTION_NAME

    public class ConfigOptions
    {
      public string BOT_ID { get; set; }
      public string BOT_PASSWORD { get; set; }
      public string CONNECTION_NAME { get; set; }
    }
    
  3. Save your changes

  4. 啟Program.cs

  5. 在檔案中,更新讀取應用程式組態的程序代碼,以包含 CONNECTION_NAME 屬性

    var config = builder.Configuration.Get<ConfigOptions>();
    builder.Configuration["MicrosoftAppType"] = "MultiTenant";
    builder.Configuration["MicrosoftAppId"] = config.BOT_ID;
    builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD;
    builder.Configuration["ConnectionName"] = config.CONNECTION_NAME;
    
  6. Save your changes

接下來,更新 Bot 程式代碼,以在運行時間使用連線設定名稱。

  1. [搜尋] 資料夾中,開 啟 [SearchApp.cs

  2. SearchApp 類別中,建立可接受 IConfiguration 物件的建構函式,並將CONNECTION_NAME屬性的值指派給名為 connectionName 的私人字段

    public class SearchApp : TeamsActivityHandler
    {
      private readonly string connectionName;
    
      public SearchApp(IConfiguration configuration)
      {
        connectionName = configuration["CONNECTION_NAME"];
      }  
    }
    
  3. Save your changes

設定產品 API 連線設定

若要使用後端 API 進行驗證,您必須在 Azure Bot 資源中設定連線設定。

繼續使用 Visual Studio 和 TeamsApp 專案:

  1. infra 資料夾中,開 啟 azure.parameters.local.json

  2. 在 檔案中,新增 backendApiEntraAppClientIdproductsApiEntraAppClientIdproductsApiEntraAppClientSecretconnectionName 參數

    {
      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "resourceBaseName": {
          "value": "bot-${{RESOURCE_SUFFIX}}-${{TEAMSFX_ENV}}"
        },
        "botEntraAppClientId": {
          "value": "${{BOT_ID}}"
        },
        "botDisplayName": {
          "value": "${{APP_DISPLAY_NAME}}"
        },
        "botAppDomain": {
          "value": "${{BOT_DOMAIN}}"
        },
        "backendApiEntraAppClientId": {
          "value": "${{BACKEND_API_ENTRA_APP_ID}}"
        },
        "productsApiEntraAppClientId": {
          "value": "${{PRODUCTS_API_ENTRA_APP_ID}}"
        },
        "productsApiEntraAppClientSecret": {
          "value": "${{SECRET_PRODUCTS_API_ENTRA_APP_CLIENT_SECRET}}"
        },
        "connectionName": {
          "value": "${{CONNECTION_NAME}}"
        }
      }
    }
    
  3. Save your changes

接下來,更新 Bicep 檔案以包含新的參數,並將其傳遞至 Azure Bot 資源。

  1. infra 資料夾中,開啟名為 azure.local.bicep 的檔案

  2. 在檔案的 botAppDomain 參數宣告之後,新增 backendApiEntraAppClientIdproductsApiEntraAppClientIdproductsApiEntraAppClientSecretconnectionName 參數宣告

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. azureBotRegistration 模組宣告中,新增參數

    module azureBotRegistration './botRegistration/azurebot.bicep' = {
      name: 'Azure-Bot-registration'
      params: {
        resourceBaseName: resourceBaseName
        botEntraAppClientId: botEntraAppClientId
        botAppDomain: botAppDomain
        botDisplayName: botDisplayName
        backendApiEntraAppClientId: backendApiEntraAppClientId
        productsApiEntraAppClientId: productsApiEntraAppClientId
        productsApiEntraAppClientSecret: productsApiEntraAppClientSecret
        connectionName: connectionName
      }
    }
    
  4. 儲存變更。

最後,更新 Bot 註冊 Bicep 檔案以包含新的連線設定。

  1. infra/botRegistration 資料夾中,開 啟 azurebot.bicep

  2. 在檔案的 botAppDomain 參數宣告之後,新增 backendApiEntraAppClientIdproductsApiEntraAppClientIdproductsApiEntraAppClientSecretconnectionName 參數宣告

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. 在 檔案中,建立名為 botServicesProductsApiConnection 的新資源

    resource botServicesProductsApiConnection 'Microsoft.BotService/botServices/connections@2022-09-15' = {
      parent: botService
      name: connectionName
      location: 'global'
      properties: {
        serviceProviderDisplayName: 'Azure Active Directory v2'
        serviceProviderId: '30dd229c-58e3-4a48-bdfd-91ec48eb906c'
        clientId: productsApiEntraAppClientId
        clientSecret: productsApiEntraAppClientSecret
        scopes: 'api://${backendApiEntraAppClientId}/Product.Read'
        parameters: [
          {
            key: 'tenantID'
            value: 'common'
          }
          {
            key: 'tokenExchangeUrl'
            value: 'api://${botAppDomain}/botid-${botEntraAppClientId}'
          }
        ]
      }
    }
    
  4. Save your changes

在訊息擴充功能中設定驗證

若要驗證訊息延伸模組中的用戶查詢,您可以使用 Bot Framework SDK 從 Bot Framework 令牌服務取得使用者的存取令牌。 存取令牌接著可用來存取外部服務的數據。

若要簡化程式代碼,請建立可處理使用者驗證的協助程序類別。

繼續使用 Visual Studio 和 ProductsPlugin 專案:

  1. 建立名為 Helpers 的新資料夾

  2. Helpers 資料夾中,建立名為 AuthHelpers.cs 的新類別檔案

  3. 在 檔案中,新增下列程序代碼:

    using Microsoft.Bot.Connector.Authentication;
    using Microsoft.Bot.Schema;
    using Microsoft.Bot.Schema.Teams;
    
    internal static class AuthHelpers
    {
        internal static async Task<MessagingExtensionResponse> CreateAuthResponse(UserTokenClient userTokenClient, string connectionName, Activity activity, CancellationToken cancellationToken)
        {
            var resource = await userTokenClient.GetSignInResourceAsync(connectionName, activity, null, cancellationToken);
    
            return new MessagingExtensionResponse
            {
                ComposeExtension = new MessagingExtensionResult
                {
                    Type = "auth",
                    SuggestedActions = new MessagingExtensionSuggestedAction
                    {
                        Actions = [
                            new() {
                                Type = ActionTypes.OpenUrl,
                                Value = resource.SignInLink,
                                Title = "Sign In",
                            },
                        ],
                    },
                },
            };
        }
    
        internal static async Task<TokenResponse> GetToken(UserTokenClient userTokenClient, string state, string userId, string channelId, string connectionName, CancellationToken cancellationToken)
        {
            var magicCode = string.Empty;
    
            if (!string.IsNullOrEmpty(state))
            {
                if (int.TryParse(state, out var parsed))
                {
                    magicCode = parsed.ToString();
                }
            }
    
            return await userTokenClient.GetUserTokenAsync(userId, connectionName, channelId, magicCode, cancellationToken);
        }
    
        internal static bool HasToken(TokenResponse tokenResponse) => tokenResponse != null && !string.IsNullOrEmpty(tokenResponse.Token);
    }
    
  4. Save your changes

AuthHelpers 類別中的三個協助程式方法會處理訊息延伸模組中的用戶驗證。

  • CreateAuthResponse 方法會建構回應,以在使用者介面中轉譯登入連結。 登入連結是使用 GetSignInResourceAsync 方法從令牌服務擷取。
  • GetToken 方法會使用令牌服務用戶端來取得目前使用者的存取令牌。 方法會使用魔術碼來驗證要求的真實性。
  • HasToken 方法會檢查來自令牌服務的回應是否包含存取令牌。 如果令牌不是 Null 或空白,則方法會傳回 true。

接下來,更新訊息延伸模組程序代碼,以使用協助程式方法來驗證用戶查詢。

  1. [搜尋] 資料夾中,開 啟 [SearchApp.cs

  2. 在檔案頂端,新增下列using語句:

    using Microsoft.Bot.Connector.Authentication;
    
  3. OnTeamsMessagingExtensionQueryAsync 方法中,於 方法的開頭新增下列程序代碼:

    var userTokenClient = turnContext.TurnState.Get<UserTokenClient>();
    var tokenResponse = await AuthHelpers.GetToken(userTokenClient, query.State, turnContext.Activity.From.Id, turnContext.Activity.ChannelId, connectionName, cancellationToken);
    
    if (!AuthHelpers.HasToken(tokenResponse))
    {
        return await AuthHelpers.CreateAuthResponse(userTokenClient, connectionName, (Activity)turnContext.Activity, cancellationToken);
    }
    
  4. Save your changes

接下來,將令牌服務網域新增至應用程式指令清單檔案,以確保用戶端可以在起始單一登錄流程時信任網域。

在 TeamsApp 專案中:

  1. appPackage 資料夾中,開 啟 manifest.json

  2. 在 檔案中,更新 validDomains 陣 列,新增令牌服務的網域:

    "validDomains": [
        "token.botframework.com",
        "${{BOT_DOMAIN}}"
    ]
    
  3. Save your changes

建立和更新資源

一切就緒之後,請執行 準備 Teams 應用程式相依性 程式,以建立新的資源並更新現有的資源。

在 Visual Studio 中繼續:

  1. [方案總管] 中,以滑鼠右鍵按兩下 TeamsApp 專案
  2. 展開 [Teams 工具組] 功能表,選取 [準備 Teams 應用程式相依性]
  3. 在 [ Microsoft 365 帳戶 ] 對話框中,選取 [ 繼續]
  4. 在 [ 布建] 對話框中,選取 [ 布建]
  5. [Teams 工具組] 警告 對話框中,選取 [ 布建]
  6. [Teams 工具組資訊 ] 對話框中,選取交叉圖示以關閉對話方塊

執行和偵錯

布建資源之後,請啟動偵錯會話來測試訊息擴充功能。

  1. 若要開始新的偵錯會話,請按 F5 或從工具列選取 [開始 ]

  2. 等候瀏覽器視窗開啟,且應用程式安裝對話框出現在 Microsoft Teams Web 用戶端中。 如果出現提示,請輸入您的Microsoft 365 帳戶認證。

  3. 在應用程式安裝對話框中,選取 [ 新增]

  4. 開啟新的或現有的Microsoft Teams 聊天

  5. 在訊息撰寫區域中,選 + 取以開啟應用程式選擇器

  6. 在應用程式清單中,選取 [Contoso 產品 ] 以開啟訊息擴充功能

  7. 在文字框中,輸入 你好

  8. 顯示訊息:您必須登入才能使用此應用程式

    以搜尋為基礎的訊息延伸模組中驗證挑戰的螢幕快照。隨即顯示登入的連結。

  9. 遵循 登入 連結來啟動驗證流程

  10. 同意要求的許可權,並返回Microsoft Teams

    [Microsoft API 許可權同意] 對話框的螢幕快照。

  11. 等候搜尋完成並顯示結果

  12. 在結果清單中,選 取 [你好 ],將卡片內嵌到撰寫消息框中

返回 Visual Studio,然後從工具列選取 [ 停止 ],或按 Shift + F5 停止偵錯會話。