Exercício – Adicionar início de sessão único

Concluído

Neste exercício, vai adicionar o início de sessão único à extensão de mensagem para autenticar consultas de utilizador.

Captura de ecrã a mostrar um desafio de autenticação numa extensão de mensagem baseada em pesquisa. É apresentada uma ligação para o início de sessão.

Configurar o registo de aplicações da API de back-end

Primeiro, crie um registo da aplicação Microsoft Entra para a API de back-end. Para efeitos deste exercício, pode criar um novo, no entanto, num ambiente de produção, utilizaria um registo de aplicações existente.

Numa janela do browser:

  1. Navegue para o portal do Azure
  2. Abra o menu do portal e selecione Microsoft Entra ID
  3. Selecione Registos de aplicações e, em seguida, selecione Novo registo
  4. No formulário Registar uma aplicação, especifique os seguintes valores:
    1. Nome: API de Produtos
    2. Tipos de conta de suporte: contas em qualquer diretório organizacional (qualquer inquilino do Microsoft Entra ID - Multi-inquilino)
  5. Selecione Registar para criar o registo de aplicações
  6. No menu esquerdo do registo de aplicações, selecione Expor uma API
  7. Selecione Adicionar e Guardar para criar um novo URI do ID da Aplicação
  8. Na secção Âmbitos definidos por esta API, selecione Adicionar um âmbito
  9. No formulário Adicionar um âmbito, especifique os seguintes valores:
    1. Nome do escopo: Product.Read
    2. Quem pode consentir?: Administradores e utilizadores
    3. Nome a apresentar do consentimento do administrador: Ler produtos
    4. Descrição do consentimento do administrador: permite que a aplicação leia os dados do produto
    5. Nome a apresentar do consentimento do utilizador: Ler produtos
    6. Descrição do consentimento do utilizador: permite que a aplicação leia dados do produto
    7. Estado: Habilitado
  10. Selecione Adicionar âmbito para criar o âmbito

Em seguida, tome nota do ID de registo da aplicação e do ID de âmbito. Precisa destes valores para configurar o registo de aplicações utilizado para obter um token de acesso para a API de back-end.

  1. No menu esquerdo do registo de aplicações, selecione Manifesto
  2. Copie o valor da propriedade appId e guarde-o para utilização posterior
  3. Copie o valor da propriedade api.oauth2PermissionScopes[0].id e guarde-o para utilização posterior

Como precisamos destes valores no projeto, adicione-os ao ficheiro de ambiente.

No Visual Studio e no projeto TeamsApp:

  1. Na pasta env , abra .env.local

  2. No ficheiro, crie as seguintes variáveis de ambiente e defina os valores para o ID de registo da aplicação e o ID de âmbito:

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

Criar um ficheiro de manifesto de registo de aplicações para autenticação com a API de back-end

Para se autenticar com a API de back-end, precisa de um registo de aplicação para obter um token de acesso com o qual chamar a API.

Em seguida, crie um ficheiro de manifesto de registo de aplicações. O manifesto define os âmbitos de permissão da API e redireciona o URI no registo de aplicações.

No Visual Studio e no projeto TeamsApp:

  1. Na pasta infra\entra , crie um ficheiro com o nome entra.products.api.manifest.json

  2. No ficheiro, adicione o seguinte código:

    {
      "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

A propriedade requiredResourceAccess especifica o ID de registo da aplicação e o ID de âmbito da API de back-end.

A propriedade replyUrlsWithType especifica o URI de redirecionamento utilizado pelo Bot Framework Token Service para devolver o token de acesso ao serviço de tokens após a autenticação do utilizador.

Em seguida, atualize o fluxo de trabalho automatizado para criar e atualizar o registo de aplicações.

No projeto TeamsApp:

  1. Abrir teamsapp.local.yml

  2. No ficheiro, localize o passo que utiliza a ação addApp/update

  3. Após a ação, adicione as ações aadApp/create e aadApp/update para criar e atualizar o registo de aplicações:

      - 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

A ação aadApp/create cria um novo registo de aplicação com o nome especificado, audiência e gera um segredo do cliente. A propriedade writeToEnvironmentFile escreve o ID de registo da aplicação, o segredo do cliente, o ID do objeto, o ID do inquilino, a autoridade e o anfitrião da autoridade nos ficheiros de ambiente. O segredo do cliente é encriptado e armazenado de forma segura no ficheiro env.local.user . O nome da variável de ambiente para o segredo do cliente tem o prefixo SECRET_, indica ao Teams Toolkit para não escrever o valor nos registos.

A ação aadApp/update atualiza o registo da aplicação com o ficheiro de manifesto especificado.

Centralizar o nome da definição de ligação

Primeiro, centralize o nome da definição de ligação no ficheiro de ambiente e atualize a configuração da aplicação para aceder ao valor da variável de ambiente no runtime.

Continuar no Visual Studio e no projeto TeamsApp:

  1. Na pasta env , abra .env.local

  2. No ficheiro, adicione o seguinte código:

    CONNECTION_NAME=ProductsAPI
    
  3. Abrir teamsapp.local.yml

  4. No ficheiro, localize o passo que utiliza a ação file/createOrUpdateJsonFile direcionada para ./appsettings. Development.json ficheiro. Atualize a matriz de conteúdos para incluir a variável de ambiente CONNECTION_NAME e escreva o valor nas appsettings. Development.json ficheiro:

      - 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

Em seguida, atualize a configuração da aplicação para aceder à variável de ambiente CONNECTION_NAME .

No projeto ProductsPlugin:

  1. Abrir Config.cs

  2. Na classe ConfigOptions , adicione uma nova propriedade com o nome 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. Abrir Program.cs

  5. No ficheiro, atualize o código que lê a configuração da aplicação para incluir a propriedade 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

Em seguida, atualize o código do bot para utilizar o nome da definição de ligação no tempo de execução.

  1. Na pasta Procurar , abra SearchApp.cs

  2. Na classe SearchApp , crie um construtor que aceite um objeto IConfiguration e atribua o valor da propriedade CONNECTION_NAME a um campo privado denominado connectionName

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

Configurar a definição de ligação da API de Produtos

Para se autenticar com a API de back-end, tem de configurar uma definição de ligação no recurso do Bot do Azure.

Continuar no Visual Studio e no projeto TeamsApp:

  1. Na pasta infra , abra azure.parameters.local.json

  2. No ficheiro, adicione os parâmetros backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    {
      "$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

Em seguida, atualize o ficheiro Bicep para incluir os novos parâmetros e transmita-os para o recurso do Bot do Azure.

  1. Na pasta infra , abra o ficheiro com o nome azure.local.bicep

  2. No ficheiro, após a declaração do parâmetro botAppDomain, adicione as declarações de parâmetro backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. Na declaração do módulo azureBotRegistration , adicione os novos parâmetros

    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. Salve suas alterações.

Por fim, atualize o ficheiro Bicep de registo do bot para incluir a nova definição de ligação.

  1. Na pasta infra/botRegistration , abra azurebot.bicep

  2. No ficheiro, após a declaração do parâmetro botAppDomain, adicione as declarações de parâmetro backendApiEntraAppClientId, productsApiEntraAppClientId, productsApiEntraAppClientSecret e connectionName

    param backendApiEntraAppClientId string
    param productsApiEntraAppClientId string
    @secure()
    param productsApiEntraAppClientSecret string
    param connectionName string
    
  3. No ficheiro, crie um novo recurso com o nome 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

Configurar a autenticação na extensão de mensagem

Para autenticar consultas de utilizador na extensão de mensagens, utilize o SDK do Bot Framework para obter um token de acesso para o utilizador a partir do Bot Framework Token Service. Em seguida, o token de acesso pode ser utilizado para aceder a dados de um serviço externo.

Para simplificar o código, crie uma classe auxiliar que processe a autenticação do utilizador.

Continuar no Visual Studio e no projeto ProductsPlugin:

  1. Criar uma nova pasta denominada Auxiliares

  2. Na pasta Helpers , crie um novo ficheiro de classe com o nome AuthHelpers.cs

  3. No ficheiro, adicione o seguinte código:

    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

Os três métodos auxiliares na classe AuthHelpers processam a autenticação do utilizador na extensão da mensagem.

  • O método CreateAuthResponse constrói uma resposta que compõe uma ligação de início de sessão na interface de utilizador. A ligação de início de sessão é obtida a partir do serviço de tokens com o método GetSignInResourceAsync .
  • O método GetToken utiliza o cliente do serviço de tokens para obter um token de acesso para o utilizador atual. O método utiliza um código mágico para verificar a autenticidade do pedido.
  • O método HasToken verifica se a resposta do serviço de tokens contém um token de acesso. Se o token não for nulo ou estiver vazio, o método devolve true.

Em seguida, atualize o código da extensão de mensagem para utilizar os métodos auxiliares para autenticar consultas de utilizador.

  1. Na pasta Procurar , abra SearchApp.cs

  2. Na parte superior do ficheiro, adicione a seguinte instrução using:

    using Microsoft.Bot.Connector.Authentication;
    
  3. No método OnTeamsMessagingExtensionQueryAsync , adicione o seguinte código no início do método:

    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

Em seguida, adicione o domínio do Serviço de Tokens ao ficheiro de manifesto da aplicação para garantir que o cliente pode confiar no domínio ao iniciar um fluxo de início de sessão único.

No projeto TeamsApp:

  1. Na pasta appPackage , abra manifest.json

  2. No ficheiro, atualize a matriz validDomains e adicione o domínio do serviço de tokens:

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

Criar e atualizar recursos

Com tudo agora implementado, execute o processo Preparar Dependências de Aplicações do Teams para criar novos recursos e atualizar os existentes.

Continuar no Visual Studio:

  1. No Explorador de Soluções, clique com o botão direito do rato no projeto TeamsApp
  2. Expanda o menu Do Teams Toolkit , selecione Preparar Dependências de Aplicações do Teams
  3. Na caixa de diálogo Conta do Microsoft 365 , selecione Continuar
  4. Na caixa de diálogo Aprovisionar, selecione Aprovisionar
  5. Na caixa de diálogo de aviso Do Teams Toolkit , selecione Aprovisionar
  6. Na caixa de diálogo Informações do Teams Toolkit , selecione o ícone cruzado para fechar a caixa de diálogo

Executar e depurar

Com os recursos aprovisionados, inicie uma sessão de depuração para testar a extensão da mensagem.

  1. Para iniciar uma nova sessão de depuração, prima F5 ou selecione Iniciar na barra de ferramentas

  2. Aguarde até que seja aberta uma janela do browser e a caixa de diálogo de instalação da aplicação seja apresentada no cliente Web do Microsoft Teams. Se lhe for pedido, introduza as credenciais da sua conta do Microsoft 365.

  3. Na caixa de diálogo de instalação da aplicação, selecione Adicionar

  4. Abrir uma nova conversa do Microsoft Teams ou existente

  5. Na área de composição de mensagens, selecione + para abrir o seletor de aplicações

  6. Na lista de aplicações, selecione Produtos Contoso para abrir a extensão de mensagem

  7. Na caixa de texto, introduza olá

  8. Uma mensagem: terá de iniciar sessão para utilizar esta aplicação .

    Captura de ecrã a mostrar um desafio de autenticação numa extensão de mensagem baseada em pesquisa. É apresentada uma ligação para o início de sessão.

  9. Siga a ligação de início de sessão para iniciar o fluxo de autenticação

  10. Consentir as permissões pedidas e regressar ao Microsoft Teams

    Captura de ecrã a mostrar a caixa de diálogo de consentimento da permissão da Microsoft Entra API.

  11. Aguarde até que a pesquisa seja concluída e que os resultados sejam apresentados

  12. Na lista de resultados, selecione olá para incorporar um cartão na caixa de mensagem de composição

Regresse ao Visual Studio e selecione Parar na barra de ferramentas ou prima Shift + F5 para parar a sessão de depuração.