Adicionar código para habilitar o SSO em seu aplicativo bot

Antes de adicionar código para habilitar o logon único (SSO), certifique-se de configurar seu aplicativo e recurso de bot no centro de administração do Microsoft Entra.

Você precisa configurar o código do seu aplicativo para obter um token de acesso do Microsoft Entra ID. O token de acesso é emitido em nome do aplicativo bot.

Observação

Se você criou seu aplicativo do Teams usando o Microsoft Teams Toolkit, poderá habilitar o SSO para seu aplicativo usando as instruções no módulo Ferramentas e SDKs. Para obter mais informações, confira Adicionar logon único ao aplicativo Teams. O Kit de Ferramentas do Teams dá suporte ao SSO para aplicativos JavaScript, TypeScript e C# no Visual Studio Code.

Esta seção cobre:

  1. Atualizar variáveis de ambiente de desenvolvimento
  2. Inicializar o aplicativo com OAuth
  3. Lidar com a entrada e o recebimento de token
  4. Lidar com falhas de entrada
  5. Lidar com a saída do usuário do aplicativo

Atualizar variáveis de ambiente de desenvolvimento

Você definiu o segredo do cliente e a configuração de conexão OAuth para o aplicativo no Microsoft Entra ID. Você deve configurar o código com esses valores.

Para atualizar as variáveis de ambiente de desenvolvimento:

  1. Abra o projeto do aplicativo bot.

  2. Abra o arquivo de ambiente (.env) do seu projeto.

  3. Atualize as seguintes variáveis:

    • Para CLIENT_ID, atualize a ID do bot do Microsoft Entra ID.
    • Para CLIENT_SECRET, atualize o segredo do cliente.
    • Para CONNECTION_NAME, atualize o nome da conexão OAuth configurada no Microsoft Entra ID.
    • Para TENANT_ID, atualize a ID do locatário.

    Observação

    Você pode personalizar a URL de redirecionamento OAuth para seu bot e provedor de identidade com base em seus requisitos de residência de dados, independentemente de seu bot estar na nuvem pública, na nuvem do Microsoft Azure Governamental ou no Microsoft Azure operado pela 21Vianet. Para URLs OAuth e lista de residência de dados, consulte Suporte a URL OAuth no Serviço de Bot de IA do Azure.

  4. Salve o arquivo.

Agora você configurou as variáveis de ambiente necessárias para seu aplicativo de bot e SSO. Em seguida, inicialize o aplicativo com OAuth.

Inicializar o aplicativo com OAuth

O SDK do Teams simplifica a inicialização do aplicativo com uma única App classe que lida com o ciclo de vida do servidor, a autenticação e a troca de token internamente.

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

Observação

A classe lida internamente com toda a App configuração do adaptador, middleware, tratamento de erros e configuração do servidor.

O usuário precisa consentir com as permissões solicitadas pelo aplicativo bot para obter o token de acesso. A caixa de diálogo de consentimento aparece com base no escopo do aplicativo.

Chats entre duas pessoas

Quando o usuário do aplicativo estiver usando o aplicativo pela primeira vez e o consentimento do usuário for necessário, a seguinte caixa de diálogo será exibida:

O infográfico mostra a interação de um bot com um usuário em escopo pessoal

Quando o usuário seleciona Continuar, um dos seguintes eventos ocorre:

  • Se a interface do usuário do bot tiver um botão de entrada, o fluxo de entrada para bots será ativado. Você pode determinar as permissões que exigem o consentimento do usuário do aplicativo. Use essa abordagem se o aplicativo exigir permissões do Graph diferentes de openid.

  • Se o bot não tiver um botão de entrada no card OAuth, o consentimento do usuário do aplicativo será necessário para um conjunto mínimo de permissões. Esse token é útil para autenticação básica e para obter o endereço de email do usuário do aplicativo.

A caixa de diálogo de consentimento que aparece é para escopos de ID aberta definidos no Microsoft Entra ID. O usuário do aplicativo deve dar consentimento apenas uma vez. Depois de consentir, o usuário do aplicativo pode acessar e usar seu aplicativo bot para as permissões e escopos concedidos.

Chats em grupo

Aqui estão os dois cenários de autenticação no escopo do grupo:

Quando um bot é adicionado a um chat de grupo pela primeira vez e o consentimento é necessário para um usuário específico, uma caixa de diálogo de consentimento é exibida somente para o usuário que @mentions adicionou o bot. O usuário deve dar consentimento único às permissões solicitadas pelo aplicativo bot para obter o token de acesso.

O usuário @mentions , o bot. Um Cartão Adaptável é exibido para solicitar o consentimento do usuário.

A imagem mostra uma caixa de diálogo de consentimento para área de trabalho

  • Se o usuário selecionar Adicionar, uma caixa de diálogo de permissões será exibida.

    A imagem mostra o pop-up de permissões solicitadas na área de trabalho

    O usuário deve selecionar Aceitar para dar consentimento.

  • Se o usuário recusar ou a solicitação expirar, o usuário deverá @mention usar o bot novamente para conceder permissão para aquisição de token. O grupo pode ver uma mensagem de bot informando que a autenticação não foi bem-sucedida.

    A imagem mostra a interação do bot quando o consentimento do usuário é negado ou a solicitação atinge o tempo limite

Se as permissões de usuário forem concedidas por padrão ou para aplicativos confiáveis, o usuário com o @mentions bot pode interagir diretamente com o bot sem precisar dar consentimento.

Observação

Depois que o usuário do aplicativo consentir, ele não precisará consentir novamente com nenhuma outra permissão. Se as permissões definidas no escopo do Microsoft Entra forem modificadas, talvez o usuário do aplicativo precise consentir novamente. Se, no entanto, a solicitação de consentimento não permitir o acesso do usuário do aplicativo, o aplicativo bot retornará ao card de entrada.

Importante

Cenários em que as caixas de diálogo de consentimento não são necessárias:

  • Se o administrador conceder consentimento em nome do locatário, os usuários do aplicativo não precisarão ser solicitados a dar consentimento. Isso significa que os usuários do aplicativo não veem as caixas de diálogo de consentimento e podem acessar o aplicativo sem problemas.
  • Se o seu aplicativo do Microsoft Entra estiver registrado no mesmo locatário do qual você está solicitando uma autenticação no Teams, o usuário do aplicativo não poderá ser solicitado a consentir e receberá um token de acesso imediatamente. Os usuários do aplicativo consentem com essas permissões somente se o aplicativo do Microsoft Entra estiver registrado em um locatário diferente.

Se você encontrar algum erro, consulte Solucionar problemas de autenticação SSO no Teams.

Lidar com a entrada e o recebimento de token

O SDK do Teams usa manipuladores simples orientados a eventos para autenticação. Use IsSignedIn para marcar o status da autenticação, SignIn() disparar o fluxo de SSO e assinar o evento para lidar com a signin autenticação bem-sucedida.

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

Observação

O SDK lida com a troca e validação de token internamente. Você não precisa mais gerenciar OAuthPromptmanualmente , WaterfallDialog, ou MainDialog classes.

Lidar com falhas de entrada

Ao usar o SSO, se a troca de token falhar, o Teams enviará uma signin/failure atividade de invocação ao seu aplicativo. O SDK inclui um manipulador padrão integrado que registra um aviso com orientações de solução de problemas acionáveis. Opcionalmente, você pode registrar seu próprio manipulador para personalizar o comportamento:

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

Lidar com a saída do usuário do aplicativo

Chame o signout método para remover o token de autenticação do usuário do cache do serviço de Token de Usuário, desconectando-o efetivamente. O SDK do Teams substitui o padrão anterior de uso DialogContextde , UserTokenClient, e CancelAllDialogsAsync por uma chamada de método simples.

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

Exemplo de código

Nome de exemplo Descrição .NET Node.js
Início rápido do SSO de conversa de bot Configure rapidamente o bot do Teams com SSO para autenticação contínua do usuário para chats individuais e em grupo. View View

Observação

OnTeamsMessagingExtensionQueryAsync e OnTeamsAppBasedLinkQueryAsync do TeamsMessagingExtensionsSearchAuthConfigBot.cs arquivo são os únicos manipuladores de SSO com suporte. Não há suporte para outros manipuladores de SSO.

Esta seção cobre:

  1. Atualizar variáveis de ambiente de desenvolvimento
  2. Adicionar código para solicitar um token
  3. Adicionar código para receber o token
  4. Adicionar token ao Repositório de Tokens do Bot Framework
  5. Lidar com logoff do usuário do aplicativo

Atualizar variáveis de ambiente de desenvolvimento

Você definiu o segredo do cliente e a configuração de conexão OAuth para o aplicativo no Microsoft Entra ID. Você deve configurar o código do aplicativo com essas variáveis.

Para atualizar as variáveis de ambiente de desenvolvimento:

  1. Abra o projeto do aplicativo.

  2. Abra o arquivo do ./env seu projeto.

  3. Atualize as seguintes variáveis:

    • Para MicrosoftAppId, atualize a ID de registro do Bot do Microsoft Entra ID.
    • Para MicrosoftAppPassword, atualize o segredo do cliente de registro do bot.
    • Para ConnectionName, atualize o nome da conexão OAuth configurada no Microsoft Entra ID.
    • Para MicrosoftAppTenantId, atualize a ID do locatário.
  4. Salve o arquivo.

Agora você configurou as variáveis de ambiente necessárias para seu aplicativo de bot e SSO. Em seguida, adicione o código para lidar com tokens.

Adicionar código para solicitar um token

A solicitação para obter o token é uma solicitação de mensagem POST usando o esquema de mensagens existente. Ele está incluído nos anexos de um OAuthCard. O esquema da classe OAuthCard é definido no Microsoft Bot Schema 4.0. O Teams atualizará o token se a TokenExchangeResource propriedade for preenchida no card. Para o canal do Teams, somente a propriedade Id, que identifica exclusivamente uma solicitação de token, é respeitada.

Observação

O Microsoft Bot Framework OAuthPrompt ou o MultiProviderAuthDialog tem suporte para autenticação de SSO.

Para atualizar o código do seu aplicativo:

  1. Adicionar trecho de código para TeamsSSOTokenExchangeMiddleware.

    Adicione o seguinte trecho de código a AdapterWithErrorHandler.cs (ou a classe equivalente no código do seu aplicativo):

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

    Observação

    Você poderá receber várias respostas para uma determinada solicitação se o usuário tiver vários pontos de extremidade ativos. Você deve eliminar todas as respostas duplicadas ou redundantes com o token. Para obter mais informações sobre signin/tokenExchange, consulte Classe TeamsSSOTokenExchangeMiddleware.

  2. Use o trecho de código a seguir para solicitar um token.

    Depois de adicionar o AdapterWithErrorHandler.cs, o seguinte código deve aparecer:

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

Se o usuário do aplicativo estiver usando seu aplicativo pela primeira vez, ele deverá consentir com a autenticação SSO.

Autenticação SSO para aplicativo de extensão de mensagem

Quando o usuário do aplicativo seleciona o nome de usuário, a permissão é concedida e ele pode usar o aplicativo.

Autenticação SSO concluída para o aplicativo de extensão de mensagem

A caixa de diálogo de consentimento que aparece é para escopos de ID aberta definidos no Microsoft Entra ID. O usuário do aplicativo deve dar consentimento apenas uma vez. Depois de consentir, o usuário do aplicativo pode acessar e usar seu aplicativo de extensão de mensagem para as permissões e escopos concedidos.

Importante

Cenários onde as caixas de diálogo de consentimento não são necessárias:

  • Se o administrador tiver concedido consentimento em nome do locatário, os usuários do aplicativo não precisarão ser solicitados a dar consentimento. Isso significa que os usuários do aplicativo não veem as caixas de diálogo de consentimento e podem acessar o aplicativo sem problemas.

Se você encontrar algum erro, consulte Solucionar problemas de autenticação SSO no Teams.

Adicionar código para receber o token

A resposta com o token é enviada por meio de uma atividade de invocação com o mesmo esquema que outras atividades de invocação que os bots recebem hoje. A única diferença é o nome da invocação, sign in/tokenExchange e o campo de valor . O campo valor contém a ID, uma cadeia de caracteres da solicitação inicial para obter o token e o campo token, um valor de cadeia de caracteres incluindo o token.

Use o seguinte exemplo de trecho de código para invocar a resposta:

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

Observação

Os trechos de código usam o bot de Caixa de Diálogo em Cascata. Para obter mais informações sobre a caixa de diálogo em cascata, consulte Sobre caixas de diálogo em cascata e de componentes.

Você recebe o token no OnTeamsMessagingExtensionQueryAsync manipulador no turnContext.Activity.Value conteúdo ou no , dependendo do cenário para o qual você está habilitando o OnTeamsAppBasedLinkQueryAsyncSSO.

JObject valueObject=JObject.FromObject(turnContext.Activity.Value);
if(valueObject["authentication"] !=null)
 {
    JObject authenticationObject=JObject.FromObject(valueObject["authentication"]);
    if(authenticationObject["token"] !=null)
 }

Validar o token de acesso

As APIs Web em seu servidor devem decodificar o token de acesso e verificar se ele é enviado do cliente.

Observação

Se você usar o Bot Framework, ele manipulará a validação do token de acesso. Se você não usar o Bot Framework, siga as diretrizes nesta seção.

Para obter mais informações sobre como validar o token de acesso, consulte Validar tokens.

Há diversas bibliotecas disponíveis que podem lidar com a validação de JWT. A validação básica inclui:

  • Verificando se o token está bem formado.
  • Verificando se o token foi emitido pela autoridade pretendida.
  • Verificar se o token está direcionado à API Web.

Ao validar o token, lembre-se das seguintes diretrizes:

  • Os tokens SSO válidos são emitidos pelo Microsoft Entra ID. A iss declaração no token deve começar com esse valor.
  • O parâmetro do aud1 token é definido como a ID do aplicativo gerada durante o registro do aplicativo Microsoft Entra.
  • O parâmetro do scp token é definido como access_as_user.

Token de acesso de exemplo

O trecho de código a seguir é uma carga decodificada típica de um token de acesso:

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

Adicionar token ao Repositório de Tokens do Bot Framework

Se estiver usando a conexão OAuth, você deverá atualizar ou adicionar o token no repositório de Tokens do Bot Framework. Adicione o seguinte exemplo de trecho de código ( TeamsMessagingExtensionsSearchAuthConfigBot.cs ou o arquivo equivalente no código do seu aplicativo) para atualizar ou adicionar o token no repositório:

Observação

Você pode encontrar o exemplo TeamsMessagingExtensionsSearchAuthConfigBot.cs em SSO de Guia, Bot e Extensão de Mensagem (ME).

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

Lidar com logoff do usuário do aplicativo

Use o trecho de código a seguir para lidar com o token de acesso caso o usuário do aplicativo faça logoff:

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

Exemplo de código

Esta seção fornece um exemplo de SDK de autenticação de bot v3.

Nome de exemplo Descrição .NET Node.js Python Manifesto
Autenticação de bot Este aplicativo de exemplo demonstra como um bot pode usar a autenticação do Teams. View View View NA
SSO de guia, bot e Extensão de mensagem (ME) Este aplicativo de exemplo demonstra a integração do SSO do Teams para Guia, Bot e Extensão de Mensagens, usando C# e o Microsoft Entra ID para autenticação segura. View View NA View
Guia, bot e extensão de mensagem Este exemplo mostra a autenticação do Microsoft Entra ID e do Facebook em bots, guias e extensões de mensagens no Microsoft Teams. View View NA View

Próxima etapa