Protección de tokens de acceso en una aplicación de página única mediante Azure API Management
En esta guía se muestra 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. Esto ayuda a proteger los tokens de acceso frente a ataques de scripting entre sitios (XSS) y a evitar que el código malintencionado se ejecute en el explorador.
Esta arquitectura usa de API Management de para:
- Implemente un patrón Backends for Frontends que obtiene un token de acceso de OAuth2 del id. de Microsoft Entra.
- Use advanced Encryption Standard AES para cifrar y descifrar el token de acceso.
- Almacene el token en una cookie de
HttpOnly
. - Proxy todas las llamadas 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 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 el almacenamiento local. Cifrar y almacenar el token de acceso en una cookie de HttpOnly
ayuda a protegerlo frente a ataques de XSS. Determinar el ámbito del dominio de API y establecer SameSite
en Strict
garantiza que la cookie se envíe automáticamente con todas las solicitudes de la API proxied.
Arquitectura
Descargar un archivo de Visio de esta arquitectura.
Flujo de trabajo
- Un usuario selecciona inicio de sesión en la aplicación de página única.
- La aplicación de página única invoca el flujo de código de autorización a través de un redireccionamiento al punto de conexión de autorización de Microsoft Entra.
- Los usuarios se autentican a sí mismos.
- Una respuesta de flujo de código de autorización con un código de autorización se redirige al punto de conexión de devolución de llamada de API Management.
- La directiva de API Management intercambia el código de autorización de un token de acceso llamando al punto de conexión del token de Microsoft Entra.
- La directiva de Azure API Management redirige a la aplicación y coloca el token de acceso cifrado en una cookie de
HttpOnly
. - El usuario invoca una llamada API externa desde la aplicación a través de un punto de conexión proxy de API Management.
- La directiva de API Management recibe la solicitud de API, descifra la cookie y realiza una llamada API de bajada, agregando el token de acceso como un encabezado
Authorization
.
Componentes
- microsoft Entra ID proporciona servicios de identidad, inicio de sesión único y autenticación multifactor en cargas de trabajo de Azure.
- API Management es una plataforma híbrida de administración multinube para las API en todos los entornos. API Management crea puertas de enlace de API coherentes y modernas para los servicios back-end existentes.
- Azure Static Web Apps es un servicio que compila e implementa automáticamente aplicaciones web de pila completa en Azure desde un repositorio de código. Las implementaciones se desencadenan mediante cambios realizados en el código fuente de la aplicación en GitHub o en repositorios de Azure DevOps.
Detalles del escenario
Las aplicaciones de página única se escriben en JavaScript y se ejecutan en el contexto de un explorador del lado 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, por lo que se pueden usar datos confidenciales, como tokens de acceso, para suplantar al usuario.
La arquitectura que se describe aquí aumenta la seguridad de las aplicaciones moviendo la adquisición y el almacenamiento de tokens al back-end y mediante una cookie de HttpOnly
cifrada 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 no se puede acceder a ellos mediante código malintencionado que se ejecuta en el explorador.
En esta arquitectura, las directivas de API Management controlan la adquisición del token de acceso y el cifrado y descifrado de la cookie. Policies 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 la cookie en una cookie de HttpOnly
ayuda a proteger el token de ataques XSS y a garantizar que JavaScript no pueda acceder a ella. Determinar el ámbito de la cookie en el dominio de API y establecer SameSite
en Strict
garantiza que la cookie se envíe automáticamente con todas las solicitudes de la API proxied. Este diseño permite que el token de acceso se agregue automáticamente al encabezado Authorization
de todas las llamadas API realizadas desde la aplicación de página única mediante el back-end.
Dado que esta arquitectura usa una cookie de SameSite=Strict
, 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. Esto se debe a que 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 agrega a la solicitud de API y la solicitud de API proxy permanece sin autenticar.
Puede configurar esta arquitectura sin usar dominios personalizados para la instancia de API Management y la aplicación web estática, pero después tendría que usar SameSite=None
para la configuración de cookies. Esta implementació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 sameSite.
Para más información sobre el uso de dominios personalizados para recursos de Azure, consulte Dominios personalizados con azure Static Web Apps y Configuración de un nombre de dominio personalizado para la instancia de Azure API Management. Para más información sobre cómo configurar registros 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 de . Para obtener un token de acceso que permita que la aplicación de página única acceda a la API, los usuarios deben autenticarse primero. Para invocar el flujo de autenticación, redirige a los usuarios al punto de conexión de autorización de Microsoft Entra. Debe configurar un URI de redireccionamiento en El identificador de Entra de Microsoft. Este URI de redirección debe ser el punto de conexión de devolución de llamada de API Management. Se pide a los usuarios que se autentiquen por sí mismos mediante el identificador de Entra de Microsoft y se redirigen de nuevo 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 intercambia el código de autorización de un token de acceso llamando al punto de conexión del token de Microsoft Entra. En el diagrama siguiente se muestra la secuencia de eventos de este flujo.
El flujo contiene estos pasos:
Para obtener un token de acceso para permitir que la aplicación de página única acceda a la API, los usuarios deben autenticarse primero. Los usuarios invocan el flujo seleccionando un botón que los redirige al punto de conexión de autorización de la plataforma de identidad de Microsoft. El
redirect_uri
se establece en el punto de conexión de la API de/auth/callback
de la puerta de enlace de API Management.Se pide 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.
El explorador se redirige al
redirect_uri
, que es el punto de conexión de devolución de llamada de API Management. El código de autorización se pasa al punto de conexión de devolución de llamada.Se invoca 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 realizando una llamada al punto de conexión del token de Microsoft Entra. Pasa 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>
El token de acceso se devuelve y almacena en una variable denominada
token
:<set-variable name="token" value="@((context.Variables.GetValueOrDefault<IResponse>("response")).Body.As<JObject>())" />
El token de acceso se cifra con el cifrado AES y se almacena 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)); }" />
La directiva de salida del punto de conexión de devolución de llamada se invoca para redirigir a la aplicación de página única. Establece el token de acceso cifrado en una cookie de
HttpOnly
que tieneSameSite
establecido enStrict
y se limita al dominio de la puerta de enlace de API Management. Dado que no se establece ninguna fecha de expiración explícita, 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 de API
Cuando la aplicación de página única tiene el token de acceso, puede usar el token para llamar a la API de bajada. Dado que la cookie tiene como ámbito el dominio de la aplicación de página única y está configurado con el atributo SameSite=Strict
, se agrega automáticamente a la solicitud. A continuación, el token de acceso se puede descifrar para que se pueda usar para llamar a la API de bajada. En el diagrama siguiente se muestra la secuencia de eventos de este flujo.
El flujo contiene estos pasos:
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 de
/graph/me
de la puerta de enlace de API Management.Dado que la cookie tiene como ámbito el dominio de la aplicación de página única y tiene
SameSite
establecido enStrict
, el explorador agrega automáticamente la cookie cuando envía la solicitud a la API.Cuando la puerta de enlace de API Management recibe la solicitud, se invoca la directiva de entrada del punto de conexión de
/graph/me
. La directiva descifra el token de acceso de la cookie y lo almacena en una variable denominadaaccess_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; } }" />
El token de acceso se agrega a la solicitud a 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>
La solicitud se envía a la API de bajada con el token de acceso agregado al encabezado
Authorization
.La respuesta de la API de bajada se devuelve directamente a la aplicación de página única.
Implementación de este escenario
Para obtener ejemplos completos de las directivas descritas aquí, junto con las especificaciones de OpenAPI y una guía de implementación completa, consulte este repositorio de GitHub .
Mejoras
Esta solución no está lista para producción. Está diseñado para demostrar lo que puede hacer mediante los servicios descritos aquí. 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 a través del cifrado AES. La clave se almacena como un secreto en la valores con nombre panel de la instancia de API Management. Para proteger mejor este valor con nombre, puede usar una referencia a un secreto almacenado en Azure Key Vault. Debe rotar periódicamente las claves de cifrado como parte de la directiva de administración de claves de .
- Este ejemplo solo llama a servidores proxy a una sola API de bajada, por lo que solo requiere un token de acceso. Este escenario permite un enfoque sin estado. Sin embargo, debido a la limitación de tamaño de las cookies HTTP, si necesita realizar llamadas proxy a varias API de bajada, necesita un enfoque con estado. En lugar de usar un único token de acceso, este enfoque implica almacenar tokens de acceso en una caché y recuperarlos en función de la API a la que se llama y una clave que se proporciona en la cookie. Puede implementar este enfoque mediante el de caché de API Management o un caché externa de Redis.
- Dado que en este ejemplo se muestra la recuperación de datos solo a través de una solicitud GET, no proporciona protección contra ataques CSRF. Si usa otros métodos HTTP, como POST, PUT, PATCH o DELETE, se requiere esta protección.
Colaboradores
Microsoft mantiene este artículo. Originalmente fue escrito por los siguientes colaboradores.
Autor principal:
- Ira Rainey | Ingeniero sénior de software
Otro colaborador:
- Mick Alberts | Escritor técnico
Para ver perfiles de LinkedIn no públicos, inicie sesión en LinkedIn.
Pasos siguientes
- Guía de implementación e implementación de ejemplo
- Directivas de en azure API Management
- Establecimiento o edición de directivas de Azure API Management
- Uso de valores con nombre en directivas de Azure API Management
- autenticación de OAuth 2.0 con el identificador de Entra de Microsoft
- ¿Qué es Azure Static Web Apps?