Compartir a través de


Uso de API Management para proteger tokens de acceso en aplicaciones de página única

En este artículo se describe cómo usar Azure API Management para implementar una arquitectura sin estado para una aplicación de página única de JavaScript que no almacena tokens en la sesión del explorador. Este enfoque ayuda a proteger los tokens de acceso frente a ataques de scripting entre sitios (XSS) y ayuda a evitar que el código malintencionado se ejecute en el explorador.

Esta arquitectura usa API Management para realizar las siguientes tareas:

  • Implemente un patrón de back-end para front-end que obtiene un token de acceso de OAuth2 de Microsoft Entra ID.
  • Uso del AES estándar de cifrado avanzado para cifrar y descifrar el token de acceso
  • Almacenar el token en una HttpOnly cookie
  • Proxy todas las llamadas de API que requieren autorización

Dado que el back-end controla la adquisición de tokens, no se requiere ningún otro código o biblioteca, como la biblioteca de autenticación de Microsoft para JavaScript (MSAL.js), en la aplicación de página única. Cuando se usa este diseño, no se almacenan tokens en la sesión del explorador ni en la solución de almacenamiento local. Cifrar y almacenar el token de acceso en una HttpOnly cookie ayuda a protegerlo de ataques XSS . La determinación del ámbito del dominio de API y establecimiento de SameSite en Strict garantizan que la cookie se envíe automáticamente con todas las solicitudes de API de primera entidad mediante proxy.

Arquitectura

Diagrama que muestra una arquitectura que no almacena tokens en el explorador.

Descargue un archivo de Visio de esta arquitectura.

Flujo de trabajo

  1. Un usuario selecciona Iniciar sesión en la aplicación de página única.

  2. La aplicación de página única invoca el flujo de código de autorización a través del redireccionamiento al punto de conexión de autorización de Microsoft Entra.

  3. Los usuarios se autentican por sí mismos.

  4. Una respuesta de flujo de código de autorización que incluye un código de autorización se redirige al punto de conexión de devolución de llamada de API Management.

  5. La directiva de API Management intercambia el código de autorización de un token de acceso mediante una llamada al punto de conexión del token de Microsoft Entra.

  6. La directiva de API Management redirige a la aplicación y coloca el token de acceso cifrado en una HttpOnly cookie.

  7. El usuario invoca una llamada API externa desde la aplicación a través de un punto de conexión mediante proxy de API Management.

  8. La directiva de API Management recibe la solicitud de API, descifra la cookie y realiza una llamada a una API descendente para agregar el token de acceso como un encabezado Authorization.

Componentes

  • Microsoft Entra ID es un servicio de administración de identidades y acceso basado en la nube que proporciona servicios de identidad, inicio de sesión único y autenticación multifactor en cargas de trabajo de Azure. En esta arquitectura, el identificador de Microsoft Entra autentica a los usuarios y emite tokens de acceso.

  • API Management es una plataforma de administración híbrida multinube para API en todo tipo de entornos. API Management crea puertas de enlace de API coherentes y modernas para los servicios back-end existentes. Esta arquitectura usa API Management para implementar un patrón de back-end para front-end que adquiere tokens de acceso de Microsoft Entra ID y redirige mediante proxy las llamadas a la API.

  • El hospedaje de sitios web estáticos en Azure Storage usa Azure Blob Storage. Es ideal para proporcionar compatibilidad con hospedaje de sitios web estáticos en casos en los que no se requiere un servidor web para representar contenido. Esta arquitectura usa el hospedaje de sitios web estáticos para hospedar la aplicación de página única. La aplicación de página única es una aplicación de JavaScript que se ejecuta en el navegador y llama a la puerta de enlace de gestión de API para acceder a la API del servidor.

Detalles del escenario

Las aplicaciones de página única se escriben en JavaScript y se ejecutan en el contexto de un explorador de cliente. En esta implementación, los usuarios pueden acceder a cualquier código que se ejecute en el explorador. El código malintencionado que se ejecuta en el explorador o un ataque XSS también puede acceder a los datos. Se puede acceder a los datos almacenados en la sesión del explorador o en el almacenamiento local. Como resultado, se pueden usar datos confidenciales como tokens de acceso para suplantar al usuario.

La arquitectura descrita en este artículo aumenta la seguridad de las aplicaciones moviendo la adquisición y el almacenamiento de tokens al back-end y mediante una cookie cifrada HttpOnly para almacenar el token de acceso. Los tokens de acceso no necesitan almacenarse en la sesión del explorador ni en el almacenamiento local, y el código malintencionado que se ejecuta en el explorador no puede acceder a ellos.

En esta arquitectura, las directivas de API Management regulan la adquisición del token de acceso, así como el cifrado y descifrado de la cookie. Las directivas son colecciones de instrucciones que se ejecutan secuencialmente en la solicitud o respuesta de una API y que se componen de elementos XML y scripts de C#.

Almacenar el token en una HttpOnly cookie ayuda a proteger el token de ataques XSS y ayuda a garantizar que JavaScript no pueda acceder a él. Limitar el ámbito de la cookie al dominio de API y establecer SameSite en Strict garantiza que la cookie se envíe automáticamente con todas las solicitudes de API de primera entidad mediante proxy. Este diseño permite que el token de acceso se agregue automáticamente al Authorization encabezado de todas las llamadas API que realiza el back-end desde la aplicación de página única.

Esta arquitectura usa una SameSite=Strict cookie, por lo que el dominio de la puerta de enlace de API Management debe ser el mismo que el dominio de la aplicación de página única. Una cookie se envía a la puerta de enlace de API Management solo cuando la solicitud de API procede de un sitio en el mismo dominio. Si los dominios son diferentes, la cookie no se agregará a la solicitud de API y la solicitud de API mediante proxy permanecerá sin autenticar.

Puede configurar esta arquitectura sin usar dominios personalizados para la instancia de API Management y el hospedaje de sitios web estáticos. En este escenario, debe usar SameSite=None para la configuración de cookies. Esta configuración da como resultado una implementación menos segura porque la cookie se agrega a todas las solicitudes a cualquier instancia de la puerta de enlace de API Management. Para obtener más información, consulte Cookies de SameSite.

Para más información sobre cómo usar dominios personalizados para recursos de Azure, consulte Asignación de un dominio personalizado a un punto de conexión de Blob Storage y Configuración de un nombre de dominio personalizado para la instancia de API Management. Para más información sobre cómo configurar registros del sistema de nombres de dominio (DNS) para dominios personalizados, consulte Administración de zonas DNS en Azure Portal.

Flujo de autenticación

Este proceso usa el flujo de código de autorización de OAuth2. Para obtener un token de acceso que permita la aplicación de página única acceder a la API, los usuarios deben autenticarse primero. Para invocar el flujo de autenticación, redirija los usuarios al punto de conexión de autorización de Microsoft Entra. Debe configurar un identificador uniforme de recursos (URI) de redirección en Microsoft Entra ID. Este URI de redirección debe ser el punto de conexión de devolución de llamada de API Management. Los usuarios deberán autenticarse mediante Microsoft Entra ID y se les redirigirá al punto de conexión de devolución de llamada de API Management con un código de autorización. A continuación, la directiva de API Management intercambiará el código de autorización de un token de acceso mediante una llamada al punto de conexión del token de Microsoft Entra ID. En el diagrama siguiente se muestra la secuencia de eventos de este flujo.

Diagrama que muestra el flujo de autenticación.

El flujo contiene los pasos siguientes:

  1. Para obtener un token de acceso que permita a la aplicación de página única acceder a la API, los usuarios deben autenticarse primero. Los usuarios invocarán el flujo seleccionando un botón que los redirige al punto de conexión de autorización de la plataforma de identidad de Microsoft. redirect_uri se establece en el punto de conexión de API /auth/callback de la puerta de enlace de API Management.

  2. Se solicitará a los usuarios que se autentiquen por sí mismos. Si la autenticación se realiza correctamente, la plataforma de identidad de Microsoft responde con un redireccionamiento.

  3. El explorador se redirigirá a redirect_uri, que es el punto de conexión de devolución de llamada de API Management. El código de autorización se transferirá al punto de conexión de devolución de llamada.

  4. Se invocará la directiva de entrada del punto de conexión de devolución de llamada. La directiva intercambia el código de autorización de un token de acceso mediante la emisión de una solicitud al punto de conexión del token de Microsoft Entra. Pasará la información necesaria, como el identificador de cliente, el secreto de cliente y el código de autorización:

    <send-request ignore-error="false" timeout="20" response-variable-name="response" mode="new">
     <set-url>https://login.microsoftonline.com/{{tenant-id}}/oauth2/v2.0/token</set-url>
     <set-method>POST</set-method>
     <set-header name="Content-Type" exists-action="override">
         <value>application/x-www-form-urlencoded</value>
     </set-header>
     <set-body>@($"grant_type=authorization_code&code={context.Request.OriginalUrl.Query.GetValueOrDefault("code")}&client_id={{client-id}}&client_secret={{client-secret}}&redirect_uri=https://{context.Request.OriginalUrl.Host}/auth/callback")</set-body>
    </send-request>
    
  5. El token de acceso se devolverá y almacenará en una variable denominada token:

    <set-variable name="token" value="@((context.Variables.GetValueOrDefault<IResponse>("response")).Body.As<JObject>())" />
    
  6. El token de acceso se cifrará mediante AES y se almacenará en una variable denominada cookie:

    <set-variable name="cookie" value="@{
        var rng = new RNGCryptoServiceProvider();
        var iv = new byte[16];
        rng.GetBytes(iv);
        byte[] tokenBytes = Encoding.UTF8.GetBytes((string)(context.Variables.GetValueOrDefault<JObject>("token"))["access_token"]);
        byte[] encryptedToken = tokenBytes.Encrypt("Aes", Convert.FromBase64String("{{enc-key}}"), iv);
        byte[] combinedContent = new byte[iv.Length + encryptedToken.Length];
        Array.Copy(iv, 0, combinedContent, 0, iv.Length);
        Array.Copy(encryptedToken, 0, combinedContent, iv.Length, encryptedToken.Length);
        return System.Net.WebUtility.UrlEncode(Convert.ToBase64String(combinedContent));
     }" />
    
  7. La directiva de salida del punto de conexión de devolución de llamada se invocará para redirigir a la aplicación de página única. Establecerá el token de acceso cifrado en una cookie HttpOnly, con SameSite establecido en Strict, y se limitará al dominio de la puerta de enlace de API Management. No se establece ninguna fecha de expiración explícita, por lo que la cookie se crea como una cookie de sesión y expira cuando se cierra el explorador.

    <return-response>
        <set-status code="302" reason="Temporary Redirect" />
        <set-header name="Set-Cookie" exists-action="override">
            <value>@($"{{cookie-name}}={context.Variables.GetValueOrDefault<string>("cookie")}; Secure; SameSite=Strict; Path=/; Domain={{cookie-domain}}; HttpOnly")</value>
        </set-header>
        <set-header name="Location" exists-action="override">
            <value>{{return-uri}}</value>
        </set-header>
    </return-response>
    

Flujo de llamadas API

Cuando la aplicación de una sola página tiene el token de acceso, puede usarlo para llamar a la API de bajada. La cookie se limita al dominio de la aplicación de página única y se configura con el SameSite=Strict atributo , por lo que se agrega automáticamente a la solicitud. A continuación, el token de acceso se puede descifrar con el fin de usarlo para llamar a la API de bajada. En el diagrama siguiente se muestra la secuencia de eventos de este flujo.

Diagrama que muestra la secuencia de llamadas API.

El flujo contiene los pasos siguientes:

  1. Un usuario selecciona un botón en la aplicación de página única para llamar a la API de bajada. Esta acción invoca una función de JavaScript que llama al punto de conexión de API /graph/me de la puerta de enlace de API Management.

  2. La cookie se limita al dominio de la aplicación de página única y tiene SameSite establecido en Strict, por lo que el explorador agregará automáticamente la cookie al enviar la solicitud a la API.

  3. Cuando la puerta de enlace de API Management recibe la solicitud, se invoca la directiva de entrada del punto de conexión /graph/me. La directiva descifrará el token de acceso de la cookie y lo almacena en una variable denominada access_token:

    <set-variable name="access_token" value="@{
        try {
            string cookie = context.Request.Headers
                .GetValueOrDefault("Cookie")?
                .Split(';')
                .ToList()?
                .Where(p => p.Contains("{{cookie-name}}"))
                .FirstOrDefault()
                .Replace("{{cookie-name}}=", "");
            byte[] encryptedBytes = Convert.FromBase64String(System.Net.WebUtility.UrlDecode(cookie));
            byte[] iv = new byte[16];
            byte[] tokenBytes = new byte[encryptedBytes.Length - 16];
            Array.Copy(encryptedBytes, 0, iv, 0, 16);
            Array.Copy(encryptedBytes, 16, tokenBytes, 0, encryptedBytes.Length - 16);
            byte[] decryptedBytes = tokenBytes.Decrypt("Aes", Convert.FromBase64String("{{enc-key}}"), iv);
            char[] convertedBytesToChar = Encoding.UTF8.GetString(decryptedBytes).ToCharArray();
            return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(convertedBytesToChar));
        } catch (Exception ex) {
            return null;
        }
    }" />
    
  4. El token de acceso se agregará a la solicitud para la API de bajada como encabezado Authorization:

    <choose>
        <when condition="@(!string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("access_token")))">
            <set-header name="Authorization" exists-action="override">
                <value>@($"Bearer {context.Variables.GetValueOrDefault<string>("access_token")}")</value>
            </set-header>
        </when>
    </choose>
    
  5. La solicitud se redirigirá mediante proxy a la API de bajada, incluido el token de acceso agregado al encabezado Authorization.

  6. La respuesta de la API de bajada se devolverá directamente a la aplicación de página única.

Implementación de este escenario

Para obtener ejemplos completos de las directivas que describe este artículo, especificaciones de OpenAPI y una guía de implementación completa, consulte este repositorio de GitHub.

Mejoras

Esta solución no está lista para el entorno de producción. Está diseñado para demostrar lo que puedes hacer mediante los servicios que describe este artículo. Tenga en cuenta los siguientes factores antes de usar la solución en producción.

  • En este ejemplo no se implementa la expiración del token de acceso ni el uso de tokens de actualización o identificador.

  • El contenido de la cookie del ejemplo se cifra mediante AES. La clave se almacena como un secreto en el panel Valores con nombre de la instancia de API Management. Para proteger mejor este valor con nombre, puede usar una referencia a un secreto que esté almacenado en Azure Key Vault. Como parte de la directiva de administración de claves, debe cambiar periódicamente las claves de cifrado.

  • En este ejemplo solo se redirigen mediante proxy las llamadas a una única API de bajada, por lo que únicamente se necesita un token de acceso. Este escenario permite adoptar un enfoque sin estado. Sin embargo, debido a la limitación de tamaño de las cookies HTTP, si necesita redirigir mediante proxy las llamadas a varias API de bajada, deberá usar un enfoque con estado.

    En lugar de usar un único token de acceso, este enfoque almacena los tokens de acceso en una caché y los recupera en función de la API a la que se llama y una clave proporcionada en la cookie. Puede emplear este enfoque mediante la caché de API Management o una caché externa de Redis.

  • En este ejemplo se muestra la recuperación de datos solo a través de una solicitud GET, por lo que no proporciona protección contra ataques de falsificación de solicitudes entre sitios (CSRF ). Si usa otros métodos HTTP, como POST, PUT, PATCH o DELETE, se requiere esta medida de protección.

Colaboradores

Microsoft mantiene este artículo. Los colaboradores siguientes escribieron este artículo.

Autor principal:

Para ver los perfiles no públicos de LinkedIn, inicie sesión en LinkedIn.

Pasos siguientes