BLOG | NGINX

Validación de tokens de acceso OAuth 2.0 con NGINX y NGINX Plus

NGINX - Parte de F5 - horizontal, negro, tipo RGB
Miniatura de Liam Crilly
Liam Crilly
Publicado el 13 de mayo de 2019

 

Imagen cortesía de John T. en unsplash.com

Hay muchas opciones para autenticar llamadas API, desde certificados de cliente X.509 hasta autenticación básica HTTP. Sin embargo, en los últimos años ha surgido un estándar de facto en forma de tokens de acceso OAuth 2.0. Se trata de credenciales de autenticación que pasan del cliente al servidor API y que normalmente se transportan como un encabezado HTTP.

Sin embargo, OAuth 2.0 es un laberinto de estándares interconectados. Los procesos para emitir, presentar y validar un flujo de autenticación OAuth 2.0 a menudo dependen de varios estándares relacionados. Al momento de escribir este artículo, hay ocho estándares OAuth 2.0 y los tokens de acceso son un buen ejemplo, ya que la especificación principal de OAuth 2.0 ( RFC 6749 ) no especifica un formato para los tokens de acceso. En el mundo real, hay dos formatos de uso común:

  • Token web JSON (JWT) según se define en RFC 7519
  • Tokens opacos que son poco más que un identificador único para un cliente autenticado

Después de la autenticación, un cliente presenta su token de acceso con cada solicitud HTTP para obtener acceso a recursos protegidos. Es necesaria la validación del token de acceso para garantizar que efectivamente fue emitido por un proveedor de identidad (IdP) confiable y que no haya expirado. Debido a que los IdP firman criptográficamente los JWT que emiten, los JWT pueden validarse “fuera de línea” sin una dependencia del tiempo de ejecución del IdP. Normalmente, un JWT también incluye una fecha de vencimiento que también se puede verificar. El módulo auth_jwt de NGINX Plus realiza la validación JWT sin conexión.

Los tokens opacos, por otro lado, deben validarse enviándolos de vuelta al IdP que los emitió. Sin embargo, esto tiene la ventaja de que el IdP puede revocar dichos tokens, por ejemplo, como parte de una operación de cierre de sesión global, sin dejar activas las sesiones iniciadas previamente. El cierre de sesión global también puede hacer que sea necesario validar los JWT con el IdP.

En este blog describimos cómo NGINX y NGINX Plus pueden actuar como una parte confiable de OAuth 2.0, enviando tokens de acceso al IdP para validación y solo redirigiendo solicitudes que pasan el proceso de validación. Analizamos los distintos beneficios de utilizar NGINX y NGINX Plus para esta tarea y cómo se puede mejorar la experiencia del usuario almacenando en caché las respuestas de validación durante un breve período. Para NGINX Plus, también mostramos cómo se puede distribuir el caché en un clúster de instancias de NGINX Plus, actualizando el almacén de clave-valor con el módulo JavaScript, como se presentó en NGINX Plus R18 .

Salvo que se indique lo contrario, la información de este blog se aplica tanto a NGINX Open Source como a NGINX Plus. Las referencias a NGINX Plus se aplican únicamente a ese producto.

Introspección simbólica

El método estándar para validar tokens de acceso con un IdP se llama introspección de tokens . RFC 7662 , OAuth 2.0 Token Introspection , es ahora un estándar ampliamente admitido que describe una interfaz JSON/REST que una parte confiada utiliza para presentar un token al IdP y describe la estructura de la respuesta. Cuenta con el respaldo de muchos de los principales proveedores de IdP y proveedores de nube.

Independientemente del formato de token que se utilice, realizar la validación en cada servicio o aplicação de backend genera una gran cantidad de código duplicado y procesamiento innecesario. Es necesario tener en cuenta diversas condiciones de error y casos extremos, y hacerlo en cada servicio backend es una receta para la inconsistencia en la implementación y, en consecuencia, una experiencia de usuario impredecible. Considere cómo cada servicio backend podría manejar las siguientes condiciones de error:

  • Token de acceso faltante
  • Token de acceso extremadamente grande
  • Caracteres no válidos o inesperados en el token de acceso
  • Se presentaron múltiples tokens de acceso
  • Desfase de reloj entre los servicios backend
Aplicações backend que realizan la validación de tokens

Uso del módulo auth_request de NGINX para validar tokens

Para evitar la duplicación de código y los problemas resultantes, podemos usar NGINX para validar tokens de acceso en nombre de los servicios de backend. Esto tiene una serie de beneficios:

  • Las solicitudes llegan a los servicios backend solo cuando el cliente ha presentado un token válido
  • Los servicios backend existentes se pueden proteger con tokens de acceso, sin necesidad de realizar cambios en el código
  • Solo la instancia NGINX (no todas las aplicaciones) necesita registrarse con el IdP
  • El comportamiento es consistente para cada condición de error, incluidos los tokens faltantes o no válidos
NGINX realiza la validación de tokens como proxy inverso.

Con NGINX actuando como un proxy inverso para una o más aplicações, podemos usar el módulo auth_request para activar una llamada API a un IdP antes de enviar una solicitud al backend. Como veremos en un momento, la siguiente solución tiene una falla fundamental, pero introduce el funcionamiento básico del módulo auth_request , que ampliaremos en secciones posteriores.

 

La directiva auth_request (línea 5) especifica la ubicación para manejar las llamadas API. La conexión al backend (línea 6) solo ocurre si la respuesta auth_request es exitosa. La ubicación de auth_request se define en la línea 9. Está marcado como interno para evitar que clientes externos accedan a él directamente.

Las líneas 11 a 14 definen varios atributos de la solicitud para que se ajuste al formato de solicitud de introspección de token. Tenga en cuenta que el token de acceso enviado en la solicitud de introspección es un componente del cuerpo definido en la línea 14. Aquí token=$http_apikey indica que el cliente debe proporcionar el token de acceso en el encabezado de solicitud de apikey . Por supuesto, el token de acceso se puede suministrar en cualquier atributo de la solicitud, en cuyo caso utilizamos una variable NGINX diferente.

Ampliación de auth_request con el módulo JavaScript de NGINX

Como se mencionó, usar el módulo auth_request de esta manera no es una solución completa. El módulo auth_request utiliza códigos de estado HTTP para determinar el éxito ( 2xx = bueno, 4xx = malo). Sin embargo, las respuestas de introspección de tokens de OAuth 2.0 codifican el éxito o el fracaso en un objeto JSON y devuelven un código de estado HTTP. 200(OK) en ambos casos.

Formato JSON de la respuesta de introspección de tokens para un token válido

Lo que necesitamos es un analizador JSON para convertir la respuesta de introspección del IdP en el código de estado HTTP apropiado para que el módulo auth_request pueda interpretar correctamente esa respuesta.

Afortunadamente, el análisis de JSON es una tarea trivial para el módulo JavaScript NGINX (njs). Entonces, en lugar de definir un bloque de ubicación para realizar la solicitud de introspección de token, le indicamos al módulo auth_request que llame a una función de JavaScript.

[ Editor : esta publicación es una de varias que exploran casos de uso del módulo JavaScript de NGINX. Para obtener una lista completa, consulte Casos de uso del módulo JavaScript NGINX .

El código de esta sección se actualiza para utilizar la directiva js_import , que reemplaza la directiva js_include en NGINX Plus R23 y versiones posteriores. Para obtener más información, consulte la documentación de referencia del módulo JavaScript de NGINX : la sección Configuración de ejemplo muestra la sintaxis correcta para la configuración de NGINX y los archivos JavaScript. ]

Nota:  Esta solución requiere que el módulo JavaScript se cargue como un módulo dinámico con la directiva load_module en nginx.conf . Para obtener instrucciones, consulte la Guía de administración de NGINX Plus .

 

La directiva js_content en la línea 13 especifica una función de JavaScript, introspectAccessToken , como el controlador auth_request . La función del controlador se define en oauth2.js :

 

Tenga en cuenta que la función introspectAccessToken realiza una subsolicitud HTTP (línea 2) a otra ubicación ( /oauth2_send_request ) que se define en el fragmento de configuración a continuación. Luego, el código JavaScript analiza la respuesta (línea 5) y envía el código de estado apropiado al módulo auth_request según el valor del campo activo . Los tokens válidos (activos) devuelven HTTP204 (Sin contenido) (pero exitoso) y los tokens no válidos devuelven HTTP403 (Prohibido) . Las condiciones de error devuelven HTTP401 (No autorizado) para que los errores puedan distinguirse de los tokens no válidos.

Nota:  Este código se proporciona únicamente como prueba de concepto y no tiene calidad de producción. A continuación se proporciona una solución completa con gestión y registro de errores integral.

La ubicación de destino de la subsolicitud definida en la línea 2 se parece mucho a nuestra configuración auth_request original.

 

Toda la configuración para construir la solicitud de introspección de token está contenida en la ubicación /_oauth2_send_request . La autenticación (línea 19), el token de acceso en sí (línea 21) y la URL para el punto final de introspección del token (línea 22) suelen ser los únicos elementos de configuración necesarios. Se requiere autenticación para que el IdP acepte solicitudes de introspección de token de esta instancia NGINX. La especificación de introspección de token OAuth 2.0 exige la autenticación, pero no especifica el método. En este ejemplo, utilizamos un token portador en el encabezado de autorización .

Con esta configuración establecida, cuando NGINX recibe una solicitud, la pasa al módulo JavaScript, que realiza una solicitud de introspección de token contra el IdP. Se inspecciona la respuesta del IdP y la autenticación se considera exitosa cuando el campo activo es verdadero . Esta solución es una forma compacta y eficiente de realizar la introspección de tokens OAuth 2.0 con NGINX y se puede adaptar fácilmente para otras API de autenticación.

Pero aún no hemos terminado. El mayor desafío con la introspección de tokens en general es que agrega latencia a todas y cada una de las solicitudes HTTP. Esto puede convertirse en un problema importante cuando el IdP en cuestión es una solución alojada o un proveedor de nube. NGINX y NGINX Plus pueden ofrecer optimizaciones a este inconveniente almacenando en caché las respuestas de introspección.

Optimización 1: Almacenamiento en caché de NGINX

La introspección del token OAuth 2.0 la proporciona el IdP en un punto final JSON/REST y, por lo tanto, la respuesta estándar es un cuerpo JSON con estado HTTP.200 . Cuando esta respuesta se vincula con el token de acceso, se vuelve altamente almacenable en caché.

Respuesta completa de introspección de tokens para un token válido

NGINX se puede configurar para almacenar en caché una copia de la respuesta de introspección para cada token de acceso, de modo que la próxima vez que se presente el mismo token de acceso, NGINX sirva la respuesta de introspección almacenada en caché en lugar de realizar una llamada API al IdP. Esto mejora enormemente la latencia general para solicitudes posteriores. Podemos controlar durante cuánto tiempo se utilizan las respuestas almacenadas en caché, para mitigar el riesgo de aceptar un token de acceso vencido o recientemente revocado. Por ejemplo, si un cliente API normalmente realiza una ráfaga de varias llamadas API durante un corto período de tiempo, entonces una validez de caché de 10 segundos podría ser suficiente para proporcionar una mejora medible en la experiencia del usuario.

El almacenamiento en caché se habilita especificando su almacenamiento: un directorio en el disco para el caché (respuestas de introspección) y una zona de memoria compartida para las claves (tokens de acceso).

 

La directiva proxy_cache_path asigna el almacenamiento necesario: /var/cache/nginx/oauth para las respuestas de introspección y una zona de memoria llamada token_responses para las claves. Se configura en el contexto http y, por lo tanto, aparece fuera de los bloques de servidor y ubicación . Luego, el almacenamiento en caché se habilita dentro del bloque de ubicación donde se procesan las respuestas de introspección del token:

 

El almacenamiento en caché está habilitado para esta ubicación con la directiva proxy_cache (línea 26). De forma predeterminada, NGINX almacena en caché según la URI, pero en nuestro caso queremos almacenar en caché la respuesta según el token de acceso presentado en el encabezado de solicitud de apikey (línea 27).

En la línea 28 usamos la directiva proxy_cache_lock para indicarle a NGINX que si llegan solicitudes simultáneas con la misma clave de caché, debe esperar hasta que la primera solicitud haya llenado el caché antes de responder a las demás. La directiva proxy_cache_valid (línea 29) le dice a NGINX cuánto tiempo debe almacenar en caché la respuesta de introspección. Sin esta directiva, NGINX determina el tiempo de almacenamiento en caché a partir de los encabezados de control de caché enviados por el IdP; sin embargo, estos no siempre son confiables, por lo que también le indicamos a NGINX que ignore los encabezados que de otra manera afectarían la forma en que almacenamos en caché las respuestas (línea 30).

Con el almacenamiento en caché ahora habilitado, un cliente que presenta un token de acceso solo sufre el costo de latencia de realizar la solicitud de introspección del token una vez cada 10 segundos.

Optimización 2: Almacenamiento en caché distribuido con NGINX Plus

Combinar el almacenamiento en caché de contenido con la introspección de tokens es una forma muy eficaz de mejorar el rendimiento general de la aplicação con un impacto insignificante en la seguridad. Sin embargo, si NGINX se implementa de manera distribuida (por ejemplo, en múltiples centros de datos, plataformas en la nube o un clúster activo-activo), las respuestas de introspección de tokens almacenados en caché solo están disponibles para la instancia de NGINX que realizó la solicitud de introspección.

Con NGINX Plus podemos utilizar el módulo keyval (un almacén de clave-valor en memoria) para almacenar en caché las respuestas de introspección de tokens. Además, también podemos sincronizar esas respuestas en un clúster de instancias NGINX Plus utilizando el módulo zone_sync . Esto significa que sin importar qué instancia de NGINX Plus realizó la solicitud de introspección de token, la respuesta está disponible en todas las instancias de NGINX Plus del clúster.

Nota:  La configuración del módulo zone_sync para compartir el estado de tiempo de ejecución está fuera del alcance de este blog. Para obtener más información sobre cómo compartir el estado en un clúster NGINX Plus, consulte la Guía de administración de NGINX Plus .

En NGINX Plus R18 y versiones posteriores, el almacén de clave-valor se puede actualizar modificando la variable que se declara en la directiva keyval . Como el módulo JavaScript tiene acceso a todas las variables NGINX, esto permite que las respuestas de introspección se completen en el almacén de clave-valor durante el procesamiento de la respuesta.

Al igual que el caché del sistema de archivos NGINX, el almacén de clave-valor se habilita especificando su almacenamiento, en este caso una zona de memoria que almacena la clave (token de acceso) y el valor (respuesta de introspección).

 

Tenga en cuenta que con el parámetro de tiempo de espera de la directiva keyval_zone especificamos el mismo período de validez de 10 segundos para las respuestas almacenadas en caché que en la línea 29 de auth_request_cache.conf , de modo que cada miembro del clúster NGINX Plus elimine independientemente la respuesta cuando expire. La línea 2 especifica el par clave-valor para cada entrada: la clave es el token de acceso suministrado en el encabezado de solicitud apikey y el valor es la respuesta de introspección evaluada por la variable $token_data .

Ahora, para cada solicitud que incluye un encabezado de solicitud apikey , la variable $token_data se completa con la respuesta de introspección de token anterior, si la hubiera. Por lo tanto, actualizamos el código JavaScript para verificar si ya tenemos una respuesta de introspección de token.

 

La línea 2 prueba si ya existe una entrada de almacén de valores clave para este token de acceso. Debido a que hay dos caminos por los cuales se puede obtener una respuesta de introspección (desde el almacén de valores clave o desde una respuesta de introspección), trasladamos la lógica de validación a la siguiente función separada, tokenResult :

 

Ahora, cada respuesta de introspección de token se guarda en el almacén de clave-valor y se sincroniza con todos los demás miembros del clúster NGINX Plus. El siguiente ejemplo muestra una solicitud HTTP simple con un token de acceso válido, seguida de una consulta a la API NGINX Plus para mostrar el contenido del almacén de clave-valor.

$ curl -IH "apikey: tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA" http://localhost/ HTTP/1.1 200 OK Fecha: Mié, 24 Abr 2019 17:41:34 GMT Tipo de contenido: aplicação/json Longitud del contenido: 612 $ curl http://localhost/api/4/http/keyvals/access_tokens {"tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA":"{\"activo\":verdadero}"}

Tenga en cuenta que el almacén de valores clave usa el formato JSON, por lo que la respuesta de introspección del token automáticamente tiene escape aplicado a las comillas.

Optimización 3: Extracción de atributos de la respuesta de introspección

Una capacidad útil de la introspección de tokens de OAuth 2.0 es que la respuesta puede contener información sobre el token además de su estado activo. Esta información incluye la fecha de expiración del token y los atributos del usuario asociado: nombre de usuario, dirección de correo electrónico, etc.

Respuesta de introspección de tokens con atributos de token

Esta información adicional puede ser muy útil. Se puede registrar, utilizar para implementar políticas de control de acceso detalladas o proporcionar a aplicações backend. Podemos exportar cada uno de estos atributos al módulo auth_request enviándolos como encabezados de respuesta adicionales con una solicitud (HTTP) exitosa.204 ) respuesta.

 

Iteramos sobre cada atributo de la respuesta de introspección (línea 23) y lo enviamos de vuelta al módulo auth_request como encabezado de respuesta. Cada nombre de encabezado tiene como prefijo Token- para evitar conflictos con los encabezados de respuesta estándar (línea 26). Estos encabezados de respuesta ahora pueden convertirse en variables NGINX y usarse como parte de la configuración regular.

 

En este ejemplo, convertimos el atributo de nombre de usuario en una nueva variable, $username (línea 11). La directiva auth_request_set nos permite exportar el contexto de la respuesta de introspección del token al contexto de la solicitud actual. El encabezado de respuesta para cada atributo (agregado por el código JavaScript) está disponible como atributo $sent_http_token_ . Luego, la línea 12 incluye el valor de $username como un encabezado de solicitud que se envía al backend. Podemos repetir esta configuración para cualquiera de los atributos devueltos en la respuesta de introspección del token.

Exportación de atributos desde la respuesta de introspección del token a la solicitud proxy.

Configuración de producción

Los ejemplos de código y configuración anteriores son funcionales y adecuados para pruebas de concepto o personalización para un caso de uso específico. Para uso en producción, recomendamos enfáticamente manejo de errores adicional, registro y configuración flexible. Puede encontrar una implementación más robusta y detallada para NGINX y NGINX Plus en nuestro repositorio de GitHub:

resumen

En este blog mostramos cómo utilizar el módulo auth_request de NGINX junto con el módulo JavaScript para realizar la introspección del token OAuth 2.0 en las solicitudes del cliente. Además, hemos ampliado esa solución con almacenamiento en caché y hemos extraído atributos de la respuesta de introspección para su uso en la configuración de NGINX.

También describimos cómo el almacén de clave-valor NGINX Plus se puede utilizar como un caché distribuido para respuestas de introspección, adecuado para implementaciones de producción en un clúster de instancias NGINX Plus.

Pruebe usted mismo la introspección de tokens OAuth 2.0 con NGINX Plus: comience hoy su prueba gratuita de 30 días o contáctenos para analizar sus casos de uso .


"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.