Construyendo una Web API REST segura con JSON Web Token en .NET (Parte I)

Publicado por Santi Macias el

MicrosoftJWTREST.NETWEB API

Viendo la cantidad de artículos y tutoriales que existen sobre como securizar un API REST, vamos a explicar en este post en qué consiste JWT (JSON Web Token) y cómo nos puede ayudar a proteger el acceso al API en aplicaciones diseñadas con .NET desde cero.

La finalidad del artículo es mostrar a todos los desarrolladores como implementar de la mejor manera con el framework .NET la programación de una API REST segura. En la primera parte, veremos la parte teórica y la segunda nos centraremos en la implementación del código.

¿Qué es un JSON Web Token?

JWT es un estándar RFC 7519 para transmitir información con la identidad y claims de un usuario de forma segura entre un cliente/servidor. Dicha información puede ser verificada y confiable porque está firmada digitalmente.

Como siempre, estas definiciones, cuando hablamos de conceptos técnicos son muy ambiguas y confunden más que ayuda a entenderlo.

Explicando JSON Web Token

A mi me gusta ir directo al grano, un JWT es "simplemente" una cadena de texto que tiene 3 partes codificadas en Base64, separadas por un punto (header.payload.firma) que generamos y entregamos a los clientes de nuestra API:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Es importante aclarar que la cadena/token esta codificado y lo crea nuestra aplicación, esto nos permite de manera muy fácil inspeccionar su contenido, por ejemplo con: JWT Debugger

Si lo copiamos y pegamos en su formulario, veremos el JSON decodificado.

Pero, puedo ver el contenido. ¿Esto es seguro?

Esta es la pregunta que todos nos hacemos la primera vez, hemos dicho que un JWT tiene 3 partes y esta codificado en Base64. Vamos a verlo:

HEADER: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 (indica el algoritmo y tipo de Token, en nuestro caso: HS256 y JWT).

PAYLOAD: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 (datos de usuario/claims, fecha creación/caducidad Token y lo que necesite nuestra API para validar la petición, recordar que nosotros generamos el token y podemos incluir todos los atributos que queramos).

SIGNATURE: TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ (la firma, para verificar que el token es válido, aqui lo importante es el "secret" con el que firmamos y que ahora explicaremos).

Asi que la respuesta es: "Sí, es totalmente seguro", porque como vemos en la imagen anterior, se indica "Signature Verified": esto quiere decir que la firma se usa para verificar que el remitente del JWT es quien dice ser y para asegurarse de que el mensaje no haya cambiado en el camino.

Si queremos información más técnica y detallada, leer esto: https://jwt.io/introduction

Explicando la firma

Como decía al principio, "la información puede ser verificada y confiable porque está firmada digitalmente", con un "secret-key". Lo importante aquí es el "secret-key" para generar la firma del token, por supuesto, no hace falta decir que nuestro "secret-key" nunca se lo daremos a nadie.

Veamos el ejemplo en el JWT Debugger cambiando "secret" por "otro-secret" en el editor y pasará lo que se muestra en la imagen.

En este caso, al cambiar el "secret-key", vemos que la firma no es válida "Invalid Signature". Esto quiere decir que no podemos confiar en el Token JWT, porque que alguien lo puede haber firmado de forma maliciosa o cambiado algo del payload.

Por supuesto, será responsibilidad de nuestra aplicación cuando recibamos un Token en nuestra API, verificarlo con nuestro "secret-key", garantizar que la firma es válida para aceptarlo o denegarlo y eso lo tenemos que programar o usar alguna libreria ya disponible, que la propia jwt.io ya pone a nuestra disposición:

Nota: La parte de como usar la librería, la implementación del token y vulnerabilidades detectadas, lo hablaremos en la segunda parte del post.

Vale Santi, pero estoy viendo la información del Token

Evidentemente, si enviamos información sensible o comprometida estamos expuestos, como buena práctica obligatoria: "todo lo que viaja por Internet ó una intranet empresarial debe estar CIFRADO"   y para ello, usaremos HTTPS para encriptar todo el tráfico entre los clientes y servidor con un certificado, de esta forma nadie puede ver el mensaje.

Así que, hay que encriptar las peticiones con un certificado y usar siempre HTTPS, pero esto no es sólo para JWT: aplica a cualquier aplicación web/móvil/desktop ó "whatever" que acceda a un API REST y así estaremos protegidos contra muchos ataques y problemas de seguridad.

Ciclo de vida de un Token JWT

Ahora que ya sabemos qué es JWT, vamos a dar un paso más allá y a conocer como funciona el proceso completo con un diagrama de uso:

El proceso completo del JWT consta de estos pasos:

  1. El usuario de una aplicacion web/móvil/desktop hace login con sus credenciales en el servidor donde esta publicada el API.
  2. El usuario es validado en el servidor y se crea un nuevo Token JWT (usando nuestro "secret-key") para entregárselo al usuario.
  3. El servidor retorna el JWT firmado que contiene los datos/claims referentes al usuario y caducidad del Token.
  4. El cliente/browser almacena el JWT para su uso y lo envia en cada petición mediante "Authorization: Bearer ".
  5. El servidor verifica la firma del Token, su caducidad y comprueba si usuario tiene permisos al recurso leyendo los datos del payload.
  6. El servidor responde al cliente la petición una vez ha confirmado el Token y los permisos del usuario son correctos.

Comentarios sobre el ciclo de vida del JWT:

  • "Authorization: Bearer ", es la forma más común, indicar que existen otras técnicas para hacerlo.
  • JWT es muy ligero: podemos codificar gran cantidad de datos sensibles en el payload y pasarlo como una cadena.
  • Creamos servicios de autenticación optimizados desacoplados del servidor y tenemos protección contra ataques CSRF.
  • Nos ahorramos mantener el estado del usuario en el servidor y lo delegamos al cliente.
  • Recordar que siempre, siempre, siempre debemos usar HTTPS entre el cliente/servidor para las peticiones.
  • Y lo más importante: ¡Nos olvidamos de cookies!

Observando el ciclo de vida de JWT, vemos que la ventaja fundamental de este modelo de seguridad, es que, en lugar de almacenar información relacionada con la autorización vinculada a cada usuario en sesión del servidor, se almacena una sola clave de firma ("secret-key") en el servidor que sirve para crear los Tokens.

Para finalizar la primera parte

El objetivo de este primer post es conocer JWT y cómo funciona su ciclo de vida. En el siguiente veremos como desarrollarlo en .NET mediante una aplicación WebApi para crear un API REST que podemos utilizar en nuestros proyectos reales y la forma correcta de implementarlo.

Con esto concluimos la primera parte de este post, si tenéis dudas o preguntas podéis dejarlas en los comentarios, o a través de nuestra cuenta de Twitter.

Happy Coding!

Autor

Santi Macias

Microsoft Tech Lead en knowmad mood, +20 años trabajando con tecnologías Microsoft actualmente centrado sobretodo en Azure, Cloud Native, DevOps, Docker, Kubernetes, Microservicios y Serverless.