¿Qué son los tokens web JSON (JWT)? ¿Por qué las API las usan?

Shutterstock.com/TierneyMJ

El estándar JSON Web Tokens (JWT) describe un método compacto para transferencias de datos verificables. Cada token contiene una firma que permite a la parte emisora ​​verificar la integridad del mensaje.

En este artículo, aprenderá qué incluye la estructura JWT y cómo puede generar sus propios tokens. Los JWT son una forma popular de proteger las API y autenticar las sesiones de los usuarios porque son simples y autónomos.

Índice de contenidos
  1. Cómo funcionan los JWT
  2. El formato JWT
  3. Crear un JWT
  4. Verificación de JWT entrantes
  5. Cuándo usar JWT
  6. Resumen

Cómo funcionan los JWT

Una de las tareas más comunes en cualquier API es validar que los usuarios sean quienes dicen ser. La autenticación generalmente se maneja haciendo que el cliente incluya una clave API con las solicitudes que envía al servidor. La clave contiene información incrustada que identifica al usuario. Esto todavía deja una gran pregunta: ¿cómo puede el servidor validar que emitió la clave en primer lugar?

Los JWT resuelven convenientemente este problema mediante el uso de un secreto para firmar cada token. El servidor puede comprobar la validez de un token intentando volver a calcular la firma presentada utilizando su secreto privado. Cualquier manipulación hará que la verificación falle.

El formato JWT

Los JWT se forman a partir de tres componentes distintos:

  • Encabezamiento - Esto incluye metadatos sobre el token en sí, como el algoritmo de firma que se utilizó.
  • Carga útil - La carga útil del token puede ser cualquier dato arbitrario relevante para su sistema. Podría incluir la identificación del usuario y una lista de características con las que pueden interactuar.
  • Firma - La firma permite validar la integridad del token en el futuro. Se crea firmando el encabezado y la carga utilizando un valor secreto que solo conoce el servidor.

Estos tres componentes se unen con puntos para producir el JWT:

header.payload.signature

Cada pieza está codificada usando Base-64. El token completo es una cadena de texto que puede consumirse fácilmente en entornos de programación y enviarse con solicitudes HTTP.

Crear un JWT

Los pasos para crear un JWT se pueden implementar en todos los lenguajes de programación. Este ejemplo usa PHP pero el proceso será similar en su propio sistema.

Comience creando el encabezado. Esto generalmente incluye dos campos, alg y typ:

  • alg - El algoritmo hash que se utilizará para crear la firma. Normalmente es HMAC SHA256 (HS256).
  • typ - El tipo de token que se está generando. Esto debería ser JWT.

Aquí está el JSON que define el encabezado:

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

El encabezado JSON debe estar codificado en Base64 a continuación:

$headerData = ["alg" => "HS256", "typ" => "JWT"];
$header = base64_encode(json_encode($headerData));

A continuación, defina la carga útil de su token como otro objeto JSON. Esto es específico de la aplicación. El ejemplo ofrece detalles de la cuenta de usuario autenticada, así como información sobre el token en sí. exp, iaty nbf son campos que se utilizan por convención para expresar la hora de caducidad del token, se emiten a la hora y no son válidos antes de la hora (de inicio). La carga útil también debe estar codificada en Base64.

$payloadData = [
    "userId" => 1001,
    "userName" => "demo",
    "licensedFeatures" => ["todos", "calendar", "invoicing"],
    "exp" => (time() + 900),
    "iat" => time(),
    "nbf" => time()
];
$payload = base64_encode(json_encode($payloadData));

Todo lo que queda es crear la firma. Para producir esto, primero combina el encabezado y la carga útil en una sola cadena separada por un . personaje:

$headerAndPayload = "$header.$payload";

A continuación, debe generar un secreto único para utilizarlo como clave de firma. El secreto debe almacenarse de forma segura en su servidor y nunca debe enviarse a los clientes. La exposición de este valor permitiría a cualquiera crear tokens válidos.

// PHP method to generate 32 random characters
$secret = bin2hex(openssl_random_pseudo_bytes(16));

Complete el proceso utilizando el secreto para firmar el encabezado combinado y la cadena de carga utilizando el algoritmo hash que indicó en el encabezado. La firma de salida debe estar codificada en Base64 como los demás componentes.

$signature = base64_encode(hash_hmac("sha256", $headerAndPayload, $secret, true));

Ahora tiene el encabezado, la carga útil y la firma como componentes de texto individuales. Únete a todos junto con . separadores para crear el JWT para enviar a su cliente:

$jwt = "$header.$payload.$signature";

Verificación de JWT entrantes

La aplicación del cliente puede determinar las funciones disponibles para el usuario al decodificar la carga útil del token. Aquí hay un ejemplo en JavaScript:

const tokenComponents = jwt.split(".");
const payload = token[1];
const payloadDecoded = JSON.parse(atob(payload));
 
// ["todos", "calendar", "invoicing"]
console.log(payloadDecoded.licensedFeatures);

Un atacante podría darse cuenta de que estos datos son texto sin formato y parecen fáciles de modificar. Podrían tratar de convencer al servidor de que tienen una función adicional cambiando la carga útil del token en su próxima solicitud:

// Create a new payload component
const modifiedPayload = btoa(JSON.stringify({
    ...payloadDecoded,
    licensedFeatures: ["todos", "calendar", "invoicing", "extraPremiumFeature"]
}));
 
// Stitch the JWT back together with the original header and signature
const newJwt = `${token[0]}.${modifiedPayload}.${token[2]}`

La respuesta a cómo se defiende el servidor de estos ataques radica en el método utilizado para generar la firma. El valor de la firma tiene en cuenta el encabezado y la carga útil del token. Modificar la carga útil, como en este ejemplo, significa que la firma ya no es válida.

El código del lado del servidor verifica los JWT entrantes al volver a calcular sus firmas. El token ha sido manipulado si la firma enviada por el cliente no coincide con el valor generado en el servidor.

$tamperedToken = $_POST["apiKey"];
list($header, $payload, $signature) = $tamperedToken;
 
// Determine the signature this token *should* have 
// when the server's secret is used as the key
$expectedSignature = hash_hmac("sha256", "$header.$payload", $secret, true);
 
// The token has been tampered with because its 
// signature is incorrect for the data it includes
if ($signature !== $expectedSignature) {
    http_response_code(403);
}
// The signatures match - we generated this 
// token and can safely trust its data
else {
    $user = fetchUserById($payload["userId"]);
}

Es imposible que un atacante genere un token válido sin acceso al secreto del servidor. Esto también significa que la pérdida accidental, o la rotación deliberada, del secreto invalidará inmediatamente todos los tokens emitidos anteriormente.

En una situación del mundo real, su código de autenticación también debe inspeccionar las marcas de tiempo de vencimiento y "no antes" en la carga útil del token. Estos se utilizan para determinar si la sesión del usuario sigue siendo válida.

Cuándo usar JWT

Los JWT se usan con frecuencia para la autenticación de API porque son sencillos de implementar en el servidor, fáciles de consumir en el cliente y simples de transmitir a través de los límites de la red. A pesar de su sencillez tienen buena seguridad porque cada token se firma con la clave secreta del servidor.

Los JWT son un mecanismo sin estado, por lo que no necesita registrar información sobre los tokens emitidos en su servidor. Puede obtener información sobre el cliente que presenta un JWT a partir de la carga útil del token, en lugar de tener que realizar una búsqueda en una base de datos. Se puede confiar en esta información de forma segura una vez que haya verificado la firma del token.

El uso de JWT es una buena opción siempre que necesite intercambiar información entre dos partes sin riesgo de manipulación. Sin embargo, hay puntos débiles a tener en cuenta: todo el sistema se verá comprometido si se filtra la clave secreta de su servidor o si su código de verificación de firma contiene un error. Por esta razón, muchos desarrolladores optan por utilizar una biblioteca de código abierto para implementar la generación y validación de JWT. Las opciones están disponibles para todos los lenguajes de programación populares. Eliminan el riesgo de supervisión cuando usted mismo verifica los tokens.

Resumen

El estándar JWT es un formato de intercambio de datos que incluye verificación de integridad integrada. Los JWT se usan comúnmente para proteger las interacciones entre los servidores API y las aplicaciones cliente. El servidor puede confiar en los tokens entrantes si puede reproducir sus firmas. Esto permite que las acciones se realicen de manera segura utilizando la información obtenida de la carga útil del token.

Los JWT son convenientes pero tienen algunos inconvenientes. La representación textual codificada en Base64 de un JWT puede volverse grande rápidamente si tiene más de un puñado de campos de carga útil. Esto podría convertirse en una sobrecarga inaceptable cuando su cliente necesite enviar el JWT con cada solicitud.

La apatridia de los JWT también es otra desventaja potencial: una vez que se emiten, los tokens son inmutables y deben usarse tal cual hasta que caduquen. Los clientes que usan cargas útiles de JWT para determinar los permisos de un usuario o las funciones con licencia necesitarán obtener un nuevo token del backend cada vez que cambien sus asignaciones.

Descubre más contenido

Subir Change privacy settings