
Existen muchas opciones para autenticar las llamadas a una API, desde certificados de cliente X.509 hasta la autenticación HTTP básica. Sin embargo, en los últimos años, OAuth 2.0 se ha convertido en un estándar de facto mediante los llamados tokens de acceso. Se trata de credenciales de autenticación que se transmiten del cliente al servidor de la API y que normalmente se incluyen en una cabecera HTTP.
Sin embargo, OAuth 2.0 es un laberinto de estándares interrelacionados. Los procesos para emitir, presentar y validar un flujo de autenticación de OAuth 2.0 suelen depender de varios estándares relacionados. En el momento de redactar este texto, existen ocho estándares de 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 define ningún formato para los tokens de acceso. En la práctica, se utilizan habitualmente dos formatos:
- 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
Tras la autenticación, el cliente presenta su token de acceso con cada solicitud HTTP para acceder a los recursos protegidos. Es necesario validar el token de acceso para garantizar que realmente lo haya emitido un proveedor de identidades (IdP) de confianza y que no haya caducado. Dado que los IdP firman criptográficamente los JWT que emiten, estos pueden validarse «sin conexión», sin depender del IdP durante la ejecución. Por lo general, un JWT también incluye una fecha de caducidad que puede comprobarse. El módulo auth_jwt de NGINX Plus valida los JWT sin conexión.
Los tokens opacos, por el contrario, deben validarse enviándolos de vuelta al proveedor de identidades (IdP) que los emitió. Sin embargo, esto ofrece 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 que las sesiones iniciadas anteriormente permanezcan activas. El cierre de sesión global también puede hacer necesario validar los JWT con el IdP.
En este blog describimos cómo NGINX y NGINX Plus pueden actuar como una parte dependiente, enviando tokens de acceso al IdP para su validación y redirigiendo únicamente las solicitudes que superen el proceso de validación. Analizamos las distintas ventajas 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 periodo. En el caso de NGINX Plus, también mostramos cómo se puede distribuir la caché entre un clúster de instancias de NGINX Plus actualizando el almacén de clave-valor con el módulo JavaScript, tal como se introdujo 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 de tokens
El método estándar para validar tokens de acceso con un IdP se denomina introspección de tokens. RFC 7662, Introspección de tokens de OAuth 2.0, es actualmente un estándar ampliamente compatible que describe una interfaz JSON/REST que una parte dependiente utiliza para presentar un token al IdP, así como la estructura de la respuesta. Es compatible con muchos de los principales proveedores de IdP y servicios en la nube.
Independientemente del formato de token utilizado, realizar la validación en cada servicio de backend o aplicación genera una gran cantidad de código duplicado y procesamiento innecesario. Es necesario tener en cuenta diversas condiciones de error y casos límite, y hacerlo en cada servicio de backend puede provocar incoherencias en la implementación y, en consecuencia, una experiencia de usuario impredecible. Veamos cómo podría gestionar cada servicio de backend las siguientes condiciones de error:
- Token de acceso ausente
- Token de acceso extremadamente grande
- Caracteres no válidos o inesperados en el token de acceso
- Se han proporcionado varios tokens de acceso
- Desfase de reloj entre los servicios de backend

Uso del módulo auth_request de NGINX para validar tokens
Para evitar la duplicación de código y los problemas derivados, podemos utilizar NGINX para validar los tokens de acceso en nombre de los servicios de backend. Esto ofrece varias ventajas:
- Las solicitudes solo llegan a los servicios de backend cuando el cliente ha presentado un token válido
- Los servicios de backend existentes se pueden proteger con tokens de acceso sin necesidad de modificar el código
- Solo es necesario registrar la instancia de NGINX (no cada aplicación) con el IdP
- El comportamiento es coherente para todas las condiciones de error, incluidos los tokens ausentes o no válidos

Con NGINX actuando como proxy inverso para una o varias aplicaciones, podemos utilizar el módulo auth_request module para activar una llamada a la API de un IdP antes de redirigir una solicitud al backend. Como veremos enseguida, la siguiente solución presenta un defecto fundamental, pero sirve para explicar el funcionamiento básico del módulo auth_request, que ampliaremos en secciones posteriores.
Cargando ejemplo de código…
La directiva auth_request (línea 5) especifica la ubicación para gestionar las llamadas a la API. Las solicitudes se redirigen al backend (línea 6) únicamente si la respuesta de auth_request es satisfactoria. La ubicación de auth_request se define en la línea 9. Está marcada como internal para impedir que los clientes externos accedan directamente a ella.
Las líneas 11–14 definen varios atributos de la solicitud para que se ajuste al formato de solicitud de introspección de tokens. Tenga en cuenta que el token de acceso enviado en la solicitud de introspección forma parte del cuerpo definido en la línea 14. En este caso, token=$http_apikey indica que el cliente debe proporcionar el token de acceso en la cabecera de solicitud apikey. Por supuesto, el token de acceso puede proporcionarse en cualquier atributo de la solicitud, en cuyo caso se utiliza una variable de NGINX diferente.
Ampliación de auth_request con el módulo JavaScript de NGINX
Como se ha mencionado, usar el módulo auth_request de esta forma no es una solución completa. El módulo auth_request utiliza códigos de estado HTTP para determinar si la operación se ha realizado correctamente (2xx = correcto, 4xx = incorrecto). 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 el código de estado HTTP 200 (OK) en ambos casos.

Lo que necesitamos es un analizador JSON que convierta la respuesta de introspección del IdP en el código de estado HTTP adecuado para que el módulo auth_request pueda interpretar correctamente dicha respuesta.
Afortunadamente, el análisis de JSON es una tarea trivial para el módulo JavaScript de NGINX (njs). Por tanto, en lugar de definir un bloque location para realizar la solicitud de introspección del token, indicamos al módulo auth_request que llame a una función de JavaScript.
[Editor – Este artículo es uno de los varios que analizan casos de uso del módulo JavaScript de NGINX. Para consultar la lista completa, consulte Casos de uso del módulo JavaScript de NGINX.
El código de esta sección se ha actualizado para utilizar la directiva js_import, que sustituye a 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 los archivos de configuración de NGINX y JavaScript.]
Nota: Esta solución requiere que el módulo JavaScript se cargue como módulo dinámico mediante la directiva load_module en nginx.conf. Para obtener instrucciones, consulte la Guía de administración de NGINX Plus.
Cargando ejemplo de código…
La directiva js_content de la línea 13 especifica una función de JavaScript, introspectAccessToken, como controlador de auth_request. La función del controlador se define en oauth2.js:
Cargando ejemplo de código…
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 que aparece a continuación. A continuación, el código JavaScript analiza la respuesta (línea 5) y devuelve el código de estado correspondiente al módulo auth_request en función del valor del campo active. Los tokens válidos (activos) devuelven el código HTTP 204 (Sin contenido) (que indica éxito), mientras que los tokens no válidos devuelven el código HTTP 403 (Prohibido). En caso de error, se devuelve el código HTTP 401 (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 la calidad necesaria para su uso en producción. Se ofrece una solución completa con un tratamiento exhaustivo de errores y registro de eventos más abajo.
La ubicación de destino de la subsolicitud definida en la línea 2 se parece mucho a nuestra configuración original de auth_request.
Cargando ejemplo de código…
Toda la configuración necesaria para crear la solicitud de introspección del token se encuentra en la ubicación /_oauth2_send_request. La autenticación (línea 19), el propio token de acceso (línea 21) y la URL del punto de conexión de introspección del token (línea 22) suelen ser los únicos elementos de configuración necesarios. La autenticación es necesaria para que el IdP acepte las solicitudes de introspección de tokens de esta instancia de NGINX. La especificación de Introspección de tokens de OAuth 2.0 exige autenticación, pero no especifica el método. En este ejemplo, utilizamos un token de portador en la cabecera Authorization.
Con esta configuración, cuando NGINX recibe una solicitud, la pasa al módulo JavaScript, que realiza una solicitud de introspección de tokens al IdP. Se examina la respuesta del IdP y la autenticación se considera correcta cuando el campo active tiene el valor true. 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 a otras API de autenticación.
Pero aún no hemos terminado. El principal reto de la introspección de tokens en general es que añade 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 servicios en la nube. NGINX y NGINX Plus pueden mitigar este inconveniente almacenando en caché las respuestas de introspección.
Optimización 1: almacenamiento en caché mediante NGINX
La introspección de tokens OAuth 2.0 la proporciona el IdP en un endpoint JSON/REST, por lo que la respuesta estándar es un cuerpo JSON con el estado HTTP 200. Cuando esta respuesta se asocia al token de acceso como clave, se puede almacenar en caché de forma muy eficiente.

NGINX se puede configurar para almacenar en caché una copia de la respuesta de introspección de cada token de acceso, de modo que, la próxima vez que se presente el mismo token, NGINX proporcione la respuesta de introspección almacenada en caché en lugar de realizar una llamada a la API del IdP. Esto reduce considerablemente la latencia general de las 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 caducado o revocado recientemente. Por ejemplo, si un cliente de API suele realizar una ráfaga de varias llamadas a la API en un breve periodo de tiempo, una validez de caché de 10 segundos podría ser suficiente para mejorar de forma perceptible la experiencia del usuario.
El almacenamiento en caché se habilita especificando dónde se almacenará: un directorio en disco para la caché (respuestas de introspección) y una zona de memoria compartida para las claves (tokens de acceso).
Cargando ejemplo de código…
La directiva proxy_cache_path asigna el almacenamiento necesario: /var/cache/nginx/oauth para las respuestas de introspección y una zona de memoria denominada token_responses para las claves. Se configura en el contexto http, por lo que aparece fuera de los bloques server y location. A continuación, el almacenamiento en caché se habilita dentro del bloque location, donde se procesan las respuestas de introspección de tokens:
Cargando ejemplo de código…
El almacenamiento en caché está habilitado para esta ubicación mediante la directiva proxy_cache (línea 26). De forma predeterminada, NGINX almacena en caché en función del URI, pero en nuestro caso queremos almacenar la respuesta en caché en función del token de acceso incluido en la cabecera de solicitud apikey (línea 27).
En la línea 28 utilizamos la directiva proxy_cache_lock para indicar a NGINX que, si llegan solicitudes simultáneas con la misma clave de caché, debe esperar a que la primera solicitud haya llenado la caché antes de responder a las demás. La directiva proxy_cache_valid (línea 29) indica a NGINX durante 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 las cabeceras de control de caché enviadas por el IdP; sin embargo, estas no siempre son fiables, por lo que también indicamos a NGINX que debe ignorar las cabeceras que, de otro modo, afectarían a la forma en que almacenamos las respuestas en caché (línea 30).
Ahora que el almacenamiento en caché está habilitado, un cliente que presenta un token de acceso solo sufre la latencia asociada a 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 aplicación con un impacto insignificante en la seguridad. Sin embargo, si NGINX se implementa de forma distribuida —por ejemplo, en varios centros de datos, plataformas en la nube o un clúster activo-activo—, las respuestas de introspección de tokens almacenadas 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 module —un almacén de pares 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 de NGINX Plus mediante el módulo zone_sync module. Esto significa que, independientemente de la instancia de NGINX Plus que haya realizado la solicitud de introspección del token, la respuesta estará disponible en todas las instancias de NGINX Plus del clúster.
Nota: La configuración del módulo zone_sync para compartir el estado en tiempo de ejecución queda fuera del alcance de este blog. Para obtener más información sobre cómo compartir el estado en un clúster de 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 declarada en la directiva keyval. Como el módulo JavaScript tiene acceso a todas las variables de NGINX, esto permite guardar las respuestas de introspección en el almacén de clave-valor durante el procesamiento de la respuesta.
Al igual que la caché del sistema de archivos de 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).
Cargando ejemplo de código…
Tenga en cuenta que, mediante el parámetro timeout de la directiva keyval_zone, especificamos para las respuestas almacenadas en caché el mismo periodo de validez de 10 segundos que en la línea 29 de auth_request_cache.conf, de modo que cada miembro del clúster de NGINX Plus elimine de forma independiente la respuesta cuando caduque. La línea 2 especifica el par clave-valor de cada entrada: la clave es el token de acceso proporcionado en la cabecera de solicitud apikey y el valor es la respuesta de introspección evaluada mediante la variable $token_data.
Ahora, para cada solicitud que incluya una cabecera de solicitud apikey, la variable $token_data se rellena con la respuesta anterior de introspección del token, si existe. Por lo tanto, actualizamos el código JavaScript para comprobar si ya disponemos de una respuesta de introspección del token.
Cargando ejemplo de código…
La línea 2 comprueba si ya existe una entrada en el almacén de pares clave-valor para este token de acceso. Dado que hay dos vías para obtener una respuesta de introspección (desde el almacén de pares clave-valor o desde una respuesta de introspección), trasladamos la lógica de validación a la siguiente función independiente, tokenResult:
Cargando ejemplo de código…
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 de NGINX Plus. En el siguiente ejemplo se muestra una solicitud HTTP sencilla con un token de acceso válido, seguida de una consulta a la API de 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
Date: Wed, 24 Apr 2019 17:41:34 GMT
Content-Type: application/json
Content-Length: 612
$ curl http://localhost/api/4/http/keyvals/access_tokens
{"tQ7AfuEFvI1yI-XNPNhjT38vg_reGkpDFA":"{\"active\":true}"}Ten en cuenta que el propio almacén de clave-valor utiliza el formato JSON, por lo que las comillas de la respuesta de introspección del token se escapan automáticamente.
Optimización 3: Extracción de atributos de la respuesta de introspección
Una funcionalidad ú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 indicar si está activo. Esta información incluye la fecha de caducidad del token y atributos del usuario asociado, como el nombre de usuario, la dirección de correo electrónico, etc.

Esta información adicional puede resultar muy útil. Se puede registrar, utilizar para implementar políticas de control de acceso detalladas o proporcionar a las aplicaciones de backend. Podemos exportar cada uno de estos atributos al módulo auth_request enviándolos como cabeceras de respuesta adicionales junto con una respuesta correcta (HTTP 204).
Cargando ejemplo de código…
Iteramos por cada atributo de la respuesta de introspección (línea 23) y lo devolvemos al módulo auth_request module como una cabecera de respuesta. Cada nombre de cabecera lleva el prefijo Token- para evitar conflictos con las cabeceras de respuesta estándar (línea 26). Estas cabeceras de respuesta ahora se pueden convertir en variables de NGINX y utilizar como parte de la configuración habitual.
Cargando ejemplo de código…
En este ejemplo, convertimos el atributo username 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. La cabecera de respuesta de cada atributo (añadida por el código JavaScript) está disponible como $sent_http_token_attribute. A continuación, la línea 12 incluye el valor de $username como una cabecera de solicitud que se envía mediante proxy al backend. Podemos repetir esta configuración para cualquiera de los atributos devueltos en la respuesta de introspección del token.

Configuración de producción
Los ejemplos de código y configuración anteriores son funcionales y adecuados para realizar pruebas de concepto o adaptarlos a un caso de uso específico. Para su uso en producción, recomendamos encarecidamente añadir gestión de errores, registro y opciones de configuración flexibles. Puede encontrar una implementación más robusta y detallada para NGINX y NGINX Plus en nuestro repositorio de GitHub:
- Introspección de tokens OAuth 2.0 con NGINX (almacenamiento en caché en disco)
- Introspección de tokens OAuth 2.0 con NGINX Plus (almacenamiento en caché de clave-valor)
Resumen
En este blog hemos mostrado cómo utilizar el módulo auth_request de NGINX junto con el módulo JavaScript para realizar la introspección de tokens OAuth 2.0 en las solicitudes de los clientes. Además, hemos ampliado esta solución mediante el almacenamiento en caché y hemos extraído atributos de la respuesta de introspección para utilizarlos en la configuración de NGINX.
También describimos cómo se puede utilizar el almacén de clave-valor de NGINX Plus como caché distribuida para las respuestas de introspección, adecuada para implementaciones en producción en un clúster de instancias de NGINX Plus.
Pruebe usted mismo la introspección de tokens OAuth 2.0 con NGINX Plus: comience hoy mismo su prueba gratuita de 30 días o póngase en contacto con nosotros para hablar sobre sus casos de uso.
Sobre el autor

Artículos relacionados del blog
Únete a F5 NGINX en AppWorld 2024
Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.
Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.
Exposición de APIs en Kubernetes
La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».
NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.
Presentamos NGINX Plus R22
NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.
Presentamos NGINX Plus R17
NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.