Consumidores, Autenticación, y Autorización de APIs en Kong – III: Autenticación JWT

Kong Gateway permite la autenticación basada en Tokens JWT (JSON Web Token), que a diferencia de otros mecanismos como la autenticación básica o por API Key, evita el envío de contraseñas en texto claro en la petición y también evita el replay, al apoyarse en la firma digital (Hash) del propio token, y permite que la comprobación de la firma digital (Hash) se puede realizar tanto con una clave secreta compartida, como con una Clave Pública, en función del algoritmo utilizado. Además es posible incluir diferentres Claims (propiedades o información en general) en el Token JWT, que pueden usarse para diferentes propósitos. La autenticación basada en Tokens JWT resulta apropiada para Clientes Confidenciales (aquellos capaces de almacenar una contraseña con seguridad, como sería el Backend de una aplicación). En este Post os explico en que consiste este mecanismo de autenticación, hacemos una introducción a los Token JWT, y vemos cómo configurarlo en Kong utilizando Konga.

Continuando con la serie de Posts acerca de Kong, en esta ocasión os voy a explicar el mecanismo de autenticación JWT (JSON Web Token) para seguidamente ver cómo configurarlo en Kong utilizando Konga, y finalmente probarlo con un curl.

La autenticación JWT proporcionada por Kong, permite asociar a un consumidor una algortimo (ej: RSA, HMAC SHA, etc), junto con una pareja Key/Secret o una Clave Pública, de tal modo, que Kong sea capaz de autenticar a los usuarios así cómo validar los Token JWT que se proporcionen en las peticiones HTTP.

Este mecanismo de autenticación es más seguro que otros métodos (ej: Basic Auth ó Key Auth), ya que no viaja la contraseña en claro y evita el replay, apropiado para su uso en Clientes Confidenciales (aquellos que son capaces de almacenar una contraseña con seguridad, como sería el Backend de una aplicación).

En comparación con HMAC Auth, la ventaja de JWT es que la comprobación de la firma digital (Hash) se puede realizar tanto con una clave secreta compartida, como con una Clave Pública, en función del algoritmo utilizado (manteniendo la Clave Privada en secreto, sin tener que compartirla con Kong).

Si quieres seguir este Post paso a paso, te recomiendo que te apoyes en el Docker Compose que compartí en GitHub (GitHub – ElWillieES – kong-docker-lab), y que sigas los anteriores Post (Introducción a Kong Gateway y Kong DashboardConsumidores, Autenticación, y Autorización de APIs en Kong – I, y Consumidores, Autenticación, y Autorización de APIs en Kong – II: Autenticación HMAC) en los que configuramos la publicación de varios Servicios (APIs) en Kong así como la configuración de autenticación básica (Basic Auth) y de clave (Key Auth) con ACLs, así como la autenticación HMAC (Hash-Based Message Authentication), y que utilizaré como punto de partida para los ejemplos que voy a realizar en este nuevo Post.

Qué es un Token JWT (JSON Web Token)?

Ya os hablé hace tiempo de los Token JWT, dentro del contexto de OAuth 2.0, los access_token, y los IdP (Keycloack, en particular). Los Token JWT son un mecanismo que pueden resultar de mucha utilidad, y no sólo en el caso de OAuth 2.0 para generar los access_token o los refresh_token.

Un Token JWT (JSON Web Token) es un standard abierto (RFC 7519) que permite crear tokens de acceso para las aplicaciones, proporcionando una forma segura de transmitir Claims (propiedades o información en general) en formato JSON entre dos partes, ya que está firmado digitalmente (Hash) con una clave (sea un clave privada o una clave secreta), por lo que se puede verificar fácilmente su integridad (con la correspondiente clave pública o con la clave secreta): si alguien modifica un Token JWT, se volverá inválido, ya que la firma no se podrá validar correctamente.

Un Token JWT está formado por tres partes separadas por puntos (aaa.bbb.ccc), cada una de las cuales está codificada en Base64:

  • Cabecera. Objeto JSON que principalmente proporciona información sobre cómo generar la firma digital del Token JWT, como el algoritmo utilizado para firmar el Token (ej: RS256 que sería RSA de 256 bits, HS256 que sería HMAC SHA256, etc.) así como el tipo de Token (ej: JWT), aunque también puede incluir más propiedades como el ID de la clave pública (kid) que deberemos utilizar para comprobar la firma del Token (en el caso de utilizar clave pública y privada, como ocurre con los access_token y los IdP).
  • Payload. Objeto JSON que consisten en el cuerpo del mensaje, formado por un conjunto de Claims (propiedades de nuestro Token, que suelen ayudar a describir a qué nos da derecho el mismo, o información en general que se desea transmitir en el Token JWT), que pueden variar según el IdP y su configuración, el registro de Aplicación y su configuración, etc., ya que básicamente podemos incluir aquí la información que deseemos. Algunos Claims comunes son los siguientes, aunque podemos invertarnos los nuestros propios:
    • exp. Fecha y hora de expiración del Token, en formato Unix Timestamp.
    • iat. Fecha y hora en la que el Token fué emitido, en formato Unix Timestamp.
    • nbf (not before).
    • jti. Identificador del Token
    • iss. Identifica el emisor del Token (la URL del IdP).
    • aud. Audiencia para la que ha sido emitido el Token.
    • sub. Es el asunto (subject), suele consistir en el ID del Usuario que identifica el Token.
  • Firma. Consiste en la firma digital (Hash) de la cabecera y cuerpo (payload) del Token utilizando el algoritmo especificado en la propia cabecera, de tal modo que nos permita validar que se trata de un Token JWT válido, y que no ha sido modificado en tránsito. Para validar la firma, obtendremos la clave pública directamente del IdP o la clave secreta utilizada (depende del caso).

Kong sólo valida los claims iss (issuer), exp (expired), y nbf (not before), teniendo en cuenta que:

  • iss (issuer) es de uso obligatorio y debe de contener la clave (key) de un Consumidor de Kong.
  • exp (expired) contiene la fecha y hora de expiración del Token en formato Unix Timestamp (epoch time).

Configuración de Autenticación JWT en Kong usando Konga

En esta ocasión queremos mantener varios tipos de autenticación, la autenticación básica (Basic Auth) a nivel Global, la de clave (Key Auth) a nivel de una ruta de un servicio con ACLs, la HMAC (HMAC Auth) a nivel de otro servicio, y JWT (JWT Auth) en otro servicio, de tal modo que puedan convivir todos estos métodos de autenticación aplicando cada uno sólo en los casos correctos, pudiendo haber usuarios que utilicen Basic Auth, API Key, HMAC, o JWT, indistintamente, en función del Endpoint y del propio usuario. Esto requiere alguna configuración especial, como se describe en la documentación de Kong (Kong Gateway – Allowing Multiple Authentication Methods), y como ya vimos y realizamos en el Post anterior (Consumidores, Autenticación, y Autorización de APIs en Kong – I)

Lo primero que vamos a hacer es habilitar la Autenticación JWT en Kong, como mecanismo de autenticación para todas las APIs del Servicio B, por lo tanto, necesitamos habilitar el Plugin de JWT Auth a nivel de dicho Servicio. Para más información acerca del Plugin de JWT Auth: Kong – Plugin – JWT Auth

Vamos a hacerlo a través de Konga. Para ello en la sección Services, entramos en el Servicio service-b, y en la pestaña de Plugins, click en Add Plugin.

Podemos elegir entre varios Plugins de Autenticación, en nuestro caso nos interesa el de JWT Auth. Click en Add plugin para continuar.

La siguiente pantalla es importante, ya que podemos configurar varias cosas, en particuar en nuestro caso nos interesa:

  • Qué claim contendrá la clave para comprobar la firma digital. Lo dejaremos en iss, su valor por defecto. En nuestro caso almacenará la Key del consumidor/usuario correspondiente.
  • Qué claims queremos verificar. Incluiremos exp, pues en caso contrario podríamos llegar a generar Tokens JWT que no expiren nunca, lo cual es una vulnerabilidad.
  • Configurar el ID del usuario anónimo (para que haga fallback y convivan múltiples mecanismos de autenticación en Kong).
  • Configurar el tiempo máximo de expiración. Por defecto es cero (infito, es decir, el Token JWT no expira nunca, lo cual es una vulnerabilidad), pudiendo especificar el valor deseado en segundos. En nuestro ejemplo, hemos configurado 300 segundos, pero en la práctica el valor apropiado dependerá de cada caso.

Click Add Pluign para continuar.

Seguidamente, crearemos unas credenciales JWT para un consumidor que usaremos para probar esta configuración. Recordemos que un Consumidor no tiene necesariamente que ser un Usuario, es decir, por ejemplo, un Consumidor podría ser una empresa (ej: un Cliente o un Proveedor) en la que creamos varias credenciales, para los diferentes usuarios y aplicaciones que consumen nuestras APIs desde dicha compañía. Incluso para una empresa, podríamos crear diferentes Consumidores con roles diferentes, y darle a cada Consumidor acceso sólo a las APIs que necesita.

En nuestro caso utilizaremos el consumidor willie que ya creamos anteriormente. Para ello, en la sección Consumers seleccionaremos el consumidor willie, en la página Credentials, iremos a la pestaña JWT, click en Create credentials. Podemos especificar el algortimo que queremos utilizar (dejaremos HS256), la key (en caso omiso se autogenerará), y el secret (en caso omiso se autogenerará). Click Submit.

Una vez creado, podemos ver todos los detalles de las credenciales que acabamos de crear.

Con esto, ya estaría todo configurado. Super fácil.

Probando la autenticación JWT

Vamos a probarlo. Para poder hacer una prueba debemos obtener el Token JWT para construir el valor que irá en el encabezado Authorization. Para esta prueba, podemos utilizar la Web de JWT.io, especificando:

  • Los datos necesarios de la cabecera (principalmente el algoritmo, en nuestro caso HS256, en el claim alg).
  • Los datos necesarios del cuerpo o payload, principalmente:
    • La Key del del usuario/consumidor que simulamos que realiza la petición, en el claim iss.
    • La fecha/hora de expiración del Token JWT, en formato Unix Timestamp, en el claim exp. Para nuestgro ejemplo podemos obtener la fecha/hora actual en formato Unix Timestamp en cualquier Web (ej: Epoch Converter) y sumarle 300 (el número de segundos que especificamos como maximum expiration en la configuración del Plugin JWT).
  • Los datos necesarios para calcular la firma digital (principalmente el secreto del usuario/consumidor que simulamos que realiza la petición).

Con esto, a la izquierda obtendremos el Token JWT, listo para utilizar.

Para probarlo, basta con ejecutar un comando curl especificando en el encabezado Authorization el Token JWT, de forma similar al siguiente ejemplo.

curl -i --location 'http://localhost:8000/service-b' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJxTnI4UURKaGV4QXU1VVFiS245TW5veE55bG9EM3BVdyIsImV4cCI6MTY3ODA0MTk3OH0.q4IKObH6Jhal0O_qXt8l5qJhhJtYPHcEYhRyxTJo3QI'

A continuación se muestra como conseguimos acceder con éxito al Servicio B (que publicamos en Kong con nuestro Dockerfile) utilizando un Token JWT en el encabezado Authorization, y seguidamente vemos como no podemos acceder si no usamos ningún tipo de autenticación (obtenemos un 401).

Pasado un rato (unos minutos) si intentamos hacer un replay, es decir, volver a ejecutar la misma petición con el mismo Token JWT, podemos comprobar que no obtenemos acceso (nos devuelve un 401), ya que el Token JWT ha expirado. Igualmente, si intentamos acceder utilizando autenticación básica (habilitada a nivel global) podemos acceder con éxito.

Bastante sencillo, ya que para trabajar con los Token JWT podremos utilizar librerías existentes para el lenguaje de programación que estemos utilizando, que nos harán la vida más sencilla, no hace falta implementarse toda esta lógica desde cero.

Despedida y Cierre

Hasta aquí llega este Post, como continuación a los anteriores Posts Introducción a Kong Gateway y Konga Dashboard y Consumidores, Autenticación, y Autorización de APIs en Kong – I, utilizando el Docker Compose compartido en GitHub para quienes deseen seguir este laboratorio paso a paso: GitHub – ElWillieES – kong-docker-lab

El objetivo de este Post era doble:

  • Por un lado, explicar la autenticación JWT (JSON Web Token) como una alternativa más segura que la Autenticación Básica o por API Key (no viaja la contraseña en claro y evita el replay), mejor opción también que la autenticación HMAC (la comprobación de la firma digital se puede realizar tanto con una clave secreta compartida, como con una Clave Pública, dependiendo del algoritmo utilizado), apropiada para su uso en Clientes Confidenciales (aquellos que son capaces de almacenar una contraseña con seguridad, como sería el Backend de una aplicación, una Web con Server-Side Rendering, etc). Además, en el caso de utilizar la clave pública, no será necesario compartir la clave privada con Kong, pudiendo mantenerla en secreto.
  • Por otro lado hemos visto con un ejemplo cómo configurar la autenticación JWT en Kong utilizando Konga, manteniendo de forma simultánea la autenticación básica, cómo generar un Token JWT utilizando la Web de JWT.io, cómo probarlo con un comando curl construyendo el encabezado Authorization, probar a hacer un replay, etc.

Poco más por hoy, como siempre confío que la lectura resulte de interés.