Agregue código para habilitar SSO en la aplicación de extensión de mensajes

Antes de agregar código para habilitar el inicio de sesión único (SSO), asegúrese de configurar la aplicación y el recurso del bot en el Centro de administración de Microsoft Entra.

Nota:

OnTeamsMessagingExtensionQueryAsync y OnTeamsAppBasedLinkQueryAsync desde el TeamsMessagingExtensionsSearchAuthConfigBot.cs archivo son los únicos controladores de SSO compatibles. No se admiten otros controladores de SSO.

Este artículo trata:

  1. Actualizar variables de entorno de desarrollo
  2. Agregar código para solicitar un token
  3. Agregue código para recibir el token
  4. Agregar token al almacén de tokens de Bot Framework
  5. Gestionar el cierre de sesión del usuario de la aplicación

Actualizar variables de entorno de desarrollo

Ha configurado el secreto de cliente y la configuración de conexión de OAuth para la aplicación en Microsoft Entra ID. Debe configurar el código de la aplicación con estas variables.

Para actualizar las variables de entorno de desarrollo:

  1. Abre el proyecto de la aplicación.

  2. Abra el ./env archivo del proyecto.

  3. Actualiza las siguientes variables:

    • Para MicrosoftAppId, actualice el identificador de registro de bot de Microsoft Entra ID.
    • Para MicrosoftAppPassword, actualice el secreto de cliente de registro de bots.
    • Para ConnectionName, actualice el nombre de la conexión OAuth que configuró en Microsoft Entra ID.
    • Para MicrosoftAppTenantId, actualice el identificador de inquilino.
  4. Guarde el archivo.

Ahora ha configurado las variables de entorno necesarias para la aplicación de bot y el SSO. A continuación, agregue el código para controlar los tokens.

Agregar código para solicitar un token

La solicitud para obtener el token es una solicitud de mensaje POST que usa el esquema de mensaje existente. Se incluye en los datos adjuntos de OAuthCard. El esquema de la clase OAuthCard se define en Microsoft Bot Schema 4.0. Teams actualiza el token si la TokenExchangeResource propiedad se rellena en la tarjeta. Para los canales de Microsoft Teams, solo se respeta la propiedad Id, que identifica de forma única una solicitud de token.

Nota:

El Microsoft Bot Framework OAuthPrompt o MultiProviderAuthDialog es compatible con la autenticación de inicio de sesión único.

Para actualizar el código de la aplicación:

  1. Agregar fragmento de código para TeamsSSOTokenExchangeMiddleware.

    Agrega el siguiente fragmento de código a AdapterWithErrorHandler.cs (o la clase equivalente en el código de tu aplicación):

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

    Nota:

    Puede recibir varias respuestas para una solicitud determinada si el usuario tiene varios puntos de conexión activos. Debe eliminar todas las respuestas duplicadas o redundantes con el token. Para obtener más información acerca de signin/tokenExchange, consulte TeamsSSOTokenExchangeMiddleware Class.

  2. Use el siguiente fragmento de código para solicitar un token.

    Después de agregar el AdapterWithErrorHandler.cs, debe aparecer el siguiente código:

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

Si el usuario de la aplicación usa la aplicación por primera vez, debe dar su consentimiento para la autenticación SSO.

Autenticación SSO para la aplicación de extensión de mensajes

Cuando el usuario de la aplicación selecciona el nombre de usuario, se concede el permiso y puede usar la aplicación.

Autenticación SSO completada para la aplicación de extensión de mensaje

El cuadro de diálogo de consentimiento que aparece es para ámbitos de id. abierto definidos en Microsoft Entra ID. El usuario de la aplicación solo debe dar su consentimiento una vez. Después de dar su consentimiento, el usuario de la aplicación puede acceder y usar la aplicación de extensión de mensajes para los permisos y ámbitos concedidos.

Importante

Escenarios en los que no se necesitan cuadros de diálogo de consentimiento:

  • Si el administrador ha concedido consentimiento en nombre del inquilino, no es necesario solicitar el consentimiento a los usuarios de la aplicación. Esto significa que los usuarios de la aplicación no ven los cuadros de diálogo de consentimiento y pueden acceder a la aplicación sin problemas.

Si encuentra algún error, consulte Solucionar problemas de autenticación SSO en Teams.

Agregue código para recibir el token

La respuesta con el token se envía a través de una actividad de invocación con el mismo esquema que otras actividades de invocación que los bots reciben hoy en día. La única diferencia es el nombre de invocación, el inicio de sesión/tokenExchange y el campo de valor . El campo valor contiene el identificador, una cadena de la solicitud inicial para obtener el token y el campo token, un valor de cadena que incluye el token.

Utilice el siguiente ejemplo de fragmento de código para invocar la respuesta:

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

Nota:

Los fragmentos de código usan el bot Cuadro de diálogo en cascada. Para obtener más información sobre el cuadro de diálogo de cascada, consulte Acerca de los cuadros de diálogo de componentes y cascada.

Recibirá el token en OnTeamsMessagingExtensionQueryAsync handler en la turnContext.Activity.Value carga útil o en el OnTeamsAppBasedLinkQueryAsync, según el escenario para el que esté habilitando el SSO.

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

Validar el token de acceso

Las API web del servidor deben descodificar el token de acceso y comprobar si se envía desde el cliente.

Nota:

Si usa Bot Framework, controla la validación del token de acceso. Si no usa Bot Framework, siga las instrucciones de esta sección.

Para obtener más información sobre cómo validar el token de acceso, consulte Validar tokens.

Hay muchas bibliotecas disponibles que pueden encargarse de la validación JWT. La validación básica incluye:

  • Comprobación de que el token está bien formado.
  • Comprobar que el token fue emitido por la autoridad prevista.
  • Comprobar que el token está destinado a la API web.

Tenga en cuenta las siguientes instrucciones para validar el token:

  • Los tokens SSO válidos son emitidos por Microsoft Entra ID. La iss notificación del token debe comenzar con este valor.
  • El parámetro del token se establece en el id. de aplicación generado durante el registro de aud1 la aplicación Microsoft Entra.
  • El parámetro del scp token se establece en access_as_user.

Ejemplo de token de acceso

El siguiente fragmento de código es una carga típica descodificada de un token de acceso:

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

Agregar token al almacén de tokens de Bot Framework

Si usa la conexión de OAuth, debe actualizar o agregar el token en el almacén de tokens de Bot Framework. Agrega el siguiente ejemplo de fragmento de código ( TeamsMessagingExtensionsSearchAuthConfigBot.cs o el archivo equivalente en el código de tu aplicación) para actualizar o agregar el token en la tienda:

Nota:

Puede encontrar el ejemplo TeamsMessagingExtensionsSearchAuthConfigBot.cs en el SSO de Tab, Bot y Extensión de mensaje (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);
            }

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

Gestionar el cierre de sesión del usuario de la aplicación

Usa el siguiente fragmento de código para controlar el token de acceso en caso de que el usuario de la aplicación cierre la sesión:

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

Ejemplo de código

En esta sección se proporcionan ejemplos del SDK de autenticación de bots v3.

Ejemplo de nombre Descripción .NET Node.js Python Manifiesto
Autenticación del bot En esta aplicación de ejemplo se muestra cómo un bot puede usar la autenticación de Teams. View View View ND
SSO de extensiones de pestañas, bots y mensajes Esta aplicación de ejemplo muestra la integración del SSO de Teams para pestañas, bots y extensiones de mensaje con C# y Microsoft Entra ID para una autenticación segura. View View ND View
Extensión de pestañas, bots y mensajes En este ejemplo se muestra la autenticación de Microsoft Entra ID y Facebook en bots, pestañas y extensiones de mensaje en Microsoft Teams. View View ND View

Paso siguiente