Introducción a JSON Web Token (JWT)

Las cookies llevan tiempo utilizándose para autentificar a los usuarios de Internet; y funcionan muy bien para ciertas aplicaciones. Sin embargo, a veces es necesaria una mayor flexibilidad. Aquí entra en juego JSON Web Token, un nuevo estándar abierto que están adoptando cada vez más sitios web y aplicaciones importantes. Te explicamos qué es un JWT, cómo funciona y en qué casos se utiliza.

¿Qué es un JSON Web Token?

Un JSON Web Token es un token de acceso estandarizado en el RFC 7519 que permite el intercambio seguro de datos entre dos partes. Contiene toda la información importante sobre una entidad, lo que implica que no hace falta consultar una base de datos ni que la sesión tenga que guardarse en el servidor (sesión sin estado).

Por este motivo, los JWT son especialmente populares en los procesos de autentificación. Con este estándar es posible cifrar mensajes cortos, dotarlos de información sobre el remitente y demostrar si este cuenta con los derechos de acceso requeridos. Los propios usuarios solo entran en contacto con el token de manera indirecta: por ejemplo, al introducir el nombre de usuario y la contraseña en una interfaz. La comunicación como tal entre las diferentes aplicaciones se lleva a cabo en el lado del cliente y del servidor.

¿Cómo se estructura un JSON Web Token?

Un JWT firmado consta de tres partes, todas ellas codificadas en Base64 y separadas por un punto:

HEADER.PAYLOAD.SIGNATURE

¿Qué significan estas tres partes?

Header

El header consta generalmente de dos valores y proporciona información importante sobre el token. Contiene el tipo de token y el algoritmo de la firma y/o cifrado utilizados. Este podría ser un ejemplo de header de un JWT:

{ "alg": "HS256", "typ": "JWT" }

Siempre se recomienda introducir JWT como tipo, que hace referencia al tipo de medio application/jwt de la IANA. En el ejemplo anterior, el header indica que HMAC-SHA256, abreviado como HS256, se utiliza para firmar el token. Otros métodos de cifrado típicos son RSA, con SHA-256 (RS256), y ECDSA, con SHA-256 (ES256). No se recomienda prescindir del cifrado, aunque sí se puede especificar none si los datos no requieren un nivel de protección alto. Los posibles valores están estandarizados por JSON-Web-Encryption según el RFC 7516.

En el caso de los JSON Web Tokens complejos firmados o cifrados, también existe el parámetro cty para content type, que se rellena del mismo modo, con el valor JWT. En el resto de casos, este parámetro se omite.

Payload

El campo payload de JSON Web Token contiene la información real que se transmitirá a la aplicación. Aquí se definen algunos estándares que determinan qué datos se transmiten y cómo. La información se proporciona como pares key/value (clave-valor); las claves se denominan claims en JWT. Hay tres tipos diferentes de claims:

  • Los claims registrados son los que figuran en el IANA JSON Web Token Claim Register y cuyo propósito se establece en un estándar. Algunos ejemplos son el emisor del token (iss, de issuer), el dominio de destino (aud, de audience) y el tiempo de vencimiento (exp, de expiration time). Se utilizan nombres de claim cortos para abreviar el token lo máximo posible.
  • Los claims públicos pueden definirse a voluntad, ya que no están sujetos a restricciones. Para que no se produzcan conflictos en la semántica de las claves, es necesario registrar los claims públicamente en el JSON Web Token Claim Register de la IANA o asignarles nombres que no puedan coincidir.
  • Los claims privados están destinados a los datos que intercambiamos especialmente con nuestras propias aplicaciones. Si bien los claims públicos contienen información como nombre o correo electrónico, los claims privados son más concretos. Por ejemplo, suelen incluir datos como identificación de usuario o nombre de departamento. Al nombrarlos, es importante asegurarse de que no vayan a entrar en conflicto con ningún claim registrado o público.

Todos los claims son opcionales, por lo que no es obligatorio utilizar todos los claims registrados. En general, el payload puede contener un número ilimitado de claims, aunque es aconsejable limitar la información del JWT al mínimo. Cuanto más extenso sea el JWT, más recursos necesitará para la codificación y la descodificación.

Un payload podría estructurarse, por lo tanto, de la siguiente manera:

{ "sub": "123", "name": "Alicia", "exp": 30 }

Firma

La firma de un JSON Web Token se crea utilizando la codificación Base64 del header y del payload, así como el método de firma o cifrado especificado. La estructura viene definida por JSON Web Signature (JWS), un estándar establecido en el RFC 7515. Para que la firma sea eficaz, es necesario utilizar una clave secreta que solo conozca la aplicación original. Por un lado, la firma verifica que el mensaje no se ha modificado por el camino. Por otro, si el token está firmado con una clave privada, también garantiza que el remitente del JWT sea el correcto.

Existen diferentes métodos de firma, dependiendo del nivel de confidencialidad de los datos:

  1. Sin protección: como hemos mencionado, si los datos no requieren un nivel de protección alto, puede especificarse el valor none en el header. En este caso, no se genera ninguna firma y el JWT solo constará de header y payload. Sin esta medida de protección, el payload puede leerse como texto en claro una vez descifrado el código Base64 y no se comprueba si el mensaje procede del remitente correcto o si fue modificado al transferirse.
  2. Firma (JWS): por lo general, basta con comprobar si los datos provienen del remitente correcto y si han sido modificados. Para ello, se utiliza el esquema JSON Web Signature (JWS), que garantiza que el mensaje no se haya cambiado por el camino y proceda del remitente correcto. Con este procedimiento, el payload también puede leerse como texto en claro tras el descifrado de Base64.
  3. Firma (JWS) y cifrado (JWE): además de JWS, es posible emplear JSON Web Encryption (JWE). JWE cifra el contenido del payload, que luego se firma con JWS. Para descifrar el contenido, se indica una contraseña común o una clave privada. De este modo, el remitente se verifica, el mensaje es confidencial y se autentifica, y el payload no puede leerse como texto en claro tras el descifrado de Base64.

El cifrado crea una secuencia de caracteres aparentemente aleatoria:

{ 7WK5T79u5mIzjIXXi2oI9Fglmgivv7RAJ7izyj9tUyQ }
Nota

En cada uno de los métodos anteriores, es necesario utilizar también SSL para proteger los datos.

¿Cómo funciona un JSON Web Token?

El inicio de sesión de usuario ejemplifica bien la función del JSON Web Token. Antes de utilizar el JWT, hay que establecer una clave secreta. Una vez que el usuario ha introducido correctamente sus credenciales, el JWT se devuelve con la clave y se guarda localmente. La transmisión debe realizarse a través de HTTPS para que los datos estén mejor protegidos.

De esta manera, cada vez que el usuario accede a recursos protegidos, como a una API o a una ruta protegida, el user agent utiliza el JWT como parámetro (por ejemplo, jwt para peticiones GET) o como header de autorización (para POST, PUT, OPTIONS y DELETE). La otra parte puede descifrar el JSON Web Token y ejecutar la solicitud si la verificación se realiza correctamente.

Nota

Puesto que el JWT se basa en datos de inicio de sesión, no debe conservarse nunca el token más tiempo del necesario ni almacenar ningún dato confidencial en la memoria del navegador.

¿En qué casos se utiliza JSON Web Token?

JSON Web Token ofrece varias ventajas en comparación con el método tradicional de autentificación y autorización con cookies, por lo que se utiliza en las siguientes situaciones:

  1. Aplicaciones REST: En las aplicaciones REST, el JWT garantiza la ausencia de estado enviando los datos de autentificación directamente con la petición.
  2. Intercambio de recursos de origen cruzado: JSON Web Token envía información mediante el llamado cross-origin resource sharing, lo cual le da una gran ventaja sobre las cookies, que no suelen enviarse con este procedimiento.
  3. Uso de varios frameworks: JSON Web Token está estandarizado y puede utilizarse una y otra vez. Cuando se emplean múltiples frameworks, los datos de autentificación pueden compartirse más fácilmente.

Ejemplo práctico de JWT

A continuación, con un ejemplo de JWT, te mostramos el aspecto final de un token. Retomamos el header que mencionamos al principio:

{
	"alg": "HS256",
	"typ": "JWT"
}

El payload del JSON Web Token podría tener el siguiente aspecto:

{
	"sub": "0123456789",
	"name": "Juan Ejemplo",
	"admin": true
}

Para lograr la estructura real del JWT (tres partes separadas por puntos), el header y el payload deben codificarse con Base64. La codificación del header se realizaría del siguiente modo:

base64Header = base64Encode(header)
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Lo mismo debe hacerse para el payload:

base64Payload = base64Encode(payload)
// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

Ahora creamos la firma. En el header, indicamos que se firme con HMAC-SHA256:

signature = HS256(base64Header + '.' + base64Payload, 'secret')
// dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao

Como último paso, estas tres partes deben unirse con puntos entre ellas:

Token = base64Header + '.' + base64Payload + '.' + signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.dyt0CoTl4WoVjAHI9Q_CwSKhl6d_9rhM3NrXuJttkao

La mayoría de los lenguajes de programación actuales proporcionan bibliotecas para generar JWT, por lo que deja de ser necesaria la conversión manual.