18-cómo funciona la firma de tokens JWT

Estructura General

Un token JWT tiene el formato:

text
header.payload.signature

Estos tres componentes están separados por puntos y codificados en Base64Url.

1. Header (Encabezado)

Contiene metadatos sobre el tipo de token y el algoritmo de firma.

Ejemplo:

json
{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg: Algoritmo de firma (HS256, RS256, etc.)

  • typ: Tipo de token (siempre "JWT")

2. Payload (Carga útil)

Contiene los claims (afirmaciones) - la información que se transmite.

Tipos de claims:

  • Registered claims: Predefinidos (iss, exp, sub, aud, etc.)

  • Public claims: Definidos por el usuario

  • Private claims: Claims personalizados

Ejemplo:

json
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1516242622
}

3. Signature (Firma)

Se crea combinando:

  • Header codificado

  • Payload codificado

  • Secret key

  • Algoritmo especificado en el header

Fórmula:

text
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Ejemplo Completo

Header codificado:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload codificado:

text
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ

Signature:

text
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Token JWT completo:

text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Características Importantes

  • ✅ Compacto: Fácil de transmitir por URL, POST, o headers HTTP

  • ✅ Auto-contenido: Toda la información necesaria está en el token

  • ✅ Verificable: La firma garantiza la integridad del contenido

  • ⚠️ No encriptado por defecto (a menos que uses JWE)

  • ⚠️ Los datos son visibles (solo Base64, no encriptación)


¿Quién provee la clave secreta?

El servidor que genera el token es quien crea y guarda la clave secreta. Es información privada que nunca se comparte con el cliente.

  • Ejemplo: Cuando te logueas en una app, el backend usa su clave secreta para firmar tu token

  • La clave debe ser: Larga, compleja y guardada de forma segura (variables de entorno)

📝 ¿Con qué datos se hace la firma?

La firma se crea usando tres componentes:

  1. Header: Metadatos (tipo de token, algoritmo usado)

  2. Payload: Los datos (user ID, permisos, expiración)

  3. Clave secreta: La llave privada del servidor

FórmulaFirma = Algoritmo(Header + "." + Payload, ClaveSecreta)

🛡️ ¿Cómo funciona el proceso?

1. Creación del token:

javascript
// En el servidor
const header = { alg: 'HS256', typ: 'JWT' };
const payload = { userId: 123, username: 'juan' };
const secretKey = 'mi-clave-super-secreta';

// Se firma concatenando header + payload + secreto
const firma = HMAC_SHA256(base64(header) + '.' + base64(payload), secretKey);

// Token final: header.payload.firma
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywidXNlcm5hbWUiOiJqdWFuIn0.firma-generada'

2. Verificación:

javascript
// Cuando recibes el token
const [receivedHeader, receivedPayload, receivedSignature] = token.split('.');

// Vuelves a calcular la firma con los mismos datos
const calculatedSignature = HMAC_SHA256(receivedHeader + '.' + receivedPayload, secretKey);

// Comparas con la firma recibida
if (calculatedSignature === receivedSignature) {
    // Token válido - no ha sido modificado
} else {
    // Token alterado - rechazar
}

🔍 Diferencia clave: Firma vs Encriptación

Firma (JWT):

  • ✅ Verifica integridad: Asegura que los datos no fueron alterados

  • ✅ Verifica autenticidad: Confirma que viene de quien dice venir

  • ❌ Los datos son visibles: Cualquiera puede leer el payload (usar Base64)

Encriptación:

  • ✅ Oculta los datos: Solo quien tiene la clave puede leerlos

  • ❌ No verifica autoría: No sabes quién lo creó

🎯 ¿Por qué se usa firma y no encriptación?

  • Performance: Verificar es más rápido que encriptar/desencriptar

  • Escalabilidad: El servidor solo necesita verificar, no desencriptar

  • Transparencia: El cliente puede leer los datos (sin poder modificarlos)

⚠️ Consideraciones de seguridad

  • Nunca poner información sensible en el payload (passwords, datos bancarios)

  • Usar HTTPS para transmitir tokens

  • Claves secretas robustas y rotadas periódicamente

  • Tiempos de expiración cortos


1. ¿Cómo se firma el token? ¿Con qué datos?

La Analogía del Sobre de Seguridad:

Imagina que quieres enviar un documento importante por correo. Para que el receptor sepa que es auténtico y no ha sido alterado, haces lo siguiente:

  1. Escribes el documento (el Payload del JWT: la información del usuario, permisos, etc.).

  2. Escribes una nota indicando el método que usaste para sellarlo, por ejemplo, "Sellado con cinta adhesiva azul" (el Header del JWT: el algoritmo de firma).

  3. Metes ambas cosas en un sobre transparente. Cualquiera puede ver el contenido (el JWT no está encriptado, es legible).

  4. Para sellarlo, usas tu cinta adhesiva azul única (la clave secreta) y pasas la cinta por toda la solapa del sobre. La cinta deja un patrón de fibras imposible de replicar sin tener el mismo rollo de cinta (la firma).

Proceso Técnico (Simplificado):

El algoritmo toma tres ingredientes:

  1. El Header codificado en Base64.

  2. El Payload codificado en Base64.

  3. La Clave Secreta (el "rollo de cinta azul").

Los combina de esta manera:

javascript
firma = algoritmoDeFirma(header + "." + payload, claveSecreta)
// Ejemplo con HS256: firma = HMACSHA256(base64Header + "." + base64Payload, "mi-clave-secreta")

La firma resultante es una cadena de caracteres única que depende de absolutamente every bit de la información del header y el payload. Si alguien modifica aunque sea una sola letra dentro del payload, la firma resultante sería completamente diferente.


2. ¿Quién da la Clave Secreta?

La Analogía del Notario:

Imagina que el token es un documento legal, como un poder notarial.

  • El "Usuario" es la persona que concede el poder.

  • El "Servidor" que genera el token es el notario. Él es la única entidad de confianza que posee el sello oficial y el libro de firmas (la clave secreta). Su trabajo es verificar la identidad del usuario, redactar el documento (crear el JWT) y sellarlo con su sello oficial (firmarlo).

  • Cualquier otro "Servidor" que reciba el documento (el token) puede confiar en él porque conoce el sello oficial del notario (conoce la clave secreta o la clave pública para verificar) y puede comprobar que el sello es auténtico y el documento no ha sido alterado.

En la práctica:

  • La clave secreta nunca abandona el servidor que la creó (el "notario").

  • Es generada y guardada de forma segura por el backend (la aplicación del servidor) que emite los JWTs. Es uno de los secretos más importantes de la aplicación.


3. Firma vs. Encriptación: La Diferencia Crucial

Esta es la confusión más común. Son conceptos totalmente distintos.

CaracterísticaFirma (JWT estándar)Encriptación (JWE)
ObjetivoGarantizar integridad y autenticidad. ¿El contenido es original y viene de quien dice?Garantizar confidencialidad. ¿Puede alguien más leer el contenido?
AnalogíaEl sobre transparente sellado con cinta. Todos pueden leer el mensaje, pero si el sello está roto, sabes que fue alterado.Una caja fuerte cerrada con combinación. No puedes ver lo que hay dentro a menos que tengas la clave para abrirla.
¿Quién puede ver el contenido?Cualquiera que tenga el token. Puedes decodificar el Base64 del header y payload en cualquier página web.Solo quien tenga la clave para desencriptar.
ProtecciónProtege contra la modificación.Protege contra la lectura.

¿Por qué se usan firmados y no encriptados la mayoría de las veces?

Porque el caso de uso típico de un JWT es para autenticación y autorización.

  1. Un cliente (navegador, app móvil) se logea y el servidor le devuelve un JWT firmado.

  2. El cliente envía ese JWT en cada petición posterior (ej. en el Header Authorization: Bearer <token>).

  3. El servidor (o un grupo de servidores) verifica la firma para asegurarse de que el token es válido y no ha sido manipulado (ej. que un usuario no cambió su ID de user123 a admin456).

  4. No es necesario esconder la información. De hecho, es útil que el payload sea legible para que el cliente o otros servicios puedan extraer información como el nombre de usuario o sus permisos sin necesidad de tener la clave secreta, solo con decodificar el Base64. La seguridad radica en que no pueden modificarla.

Resumen final:

  • Firmar es como poner un sello de seguridad en un documento público. Verifica que el documento es original.

  • Encriptar es como meter un documento en una caja fuerte. Esconde el contenido de miradas indiscretas.

Un JWT estándar (JWS) viene con un sello de seguridad (firma), pero en un sobre transparente (sin encriptar). Si necesitas privacidad, existe un estándar llamado JWE (JSON Web Encryption) que sí encripta el contenido.


Excelente pregunta. La clave secreta (o secret keyNO va dentro del payload ni del header del JWT. Es un error común pensar que se incluye ahí, pero hacerlo sería una grave vulnerabilidad de seguridad.

La clave secreta es un dato externo y privado que solo deben conocer:

  1. El servidor que genera (firma) el token (por ejemplo, tu backend/API).

  2. El servidor que verifica (valida) el token (de nuevo, tu backend/API).

¿Dónde y cómo se usa entonces la Clave Secreta?

La clave secreta se utiliza exclusivamente para crear y verificar la Firma (la tercera parte del JWT).

El proceso es el siguiente:

1. Para CREAR (firmar) el JWT:

  1. Se crean el header y el payload en formato JSON.

  2. Se codifican ambos en Base64Url para formar encodedHeader y encodedPayload.

  3. Se concatenan con un punto: encodedHeader + "." + encodedPayload.

  4. Se toma esta cadena concatenada y se aplica el algoritmo de firma (el especificado en header.alg, como HS256) junto con la clave secreta para generar la firma.

Fórmula de la firma (ejemplo para HS256):

javascript
signature = HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  **secretKey** // <- ¡Aquí se usa la clave!
);
  1. Finalmente, se codifica esta firma en Base64Url y se junta todo:
    encodedHeader + "." + encodedPayload + "." + encodedSignature

2. Para VERIFICAR el JWT:

Cuando el cliente (navegador, app móvil) te envía el JWT en una solicitud, tu servidor debe verificar que es auténtico y no ha sido alterado.

  1. El servidor separa el token en sus tres partes: headerpayload y signature.

  2. Toma las primeras dos partes (header y payload codificados) y, usando la misma clave secreta que usó para firmar, vuelve a calcular la firma de la misma manera que en el paso 1.

  3. Compara la firma que acaba de calcular con la firma que vino en el token.

    • Si coinciden: Significa que el token es auténtico y su contenido (header y payload) no ha sido modificado. El servidor puede confiar en la información del payload (como el id del usuario).

    • Si NO coinciden: Significa que el token fue alterado o no fue firmado con la clave correcta. El servidor debe rechazar la solicitud inmediatamente.

Analogía: Un documento firmado

Piensa en el JWT como un documento legal:

  • El Header es la portada que dice "Contrato".

  • El Payload es el contenido del contrato (cláusulas, nombres, fechas).

  • La Firma es tu firma manuscrita al final del documento.

  • La Clave Secreta es el bolígrafo con tinta única e imposible de copiar que usas para firmar.

Tú no escribes el bolígrafo dentro del contrato. El bolígrafo es tu herramienta privada para demostrar que fuiste tú quien autenticó el contenido del documento. Cualquiera puede leer el contrato (header y payload), pero solo quien tenga el bolígrafo original puede verificar si la firma es válida.

Resumen crucial:

  • ✅ CORRECTO: La clave secreta es un dato externo, guardado de forma segura en tu servidor (en variables de entorno o un gestor de secretos). Se usa para generar y verificar la firma.

  • ❌ INCORRECTO (Y MUY PELIGROSO): Incluir la clave secreta dentro del payload o header. Si haces esto, cualquier persona que decodee el JWT (algo muy fácil) podrá robar tu clave y firmar tokens falsos, comprometiendo toda tu aplicación.

En tu ejemplo, los únicos datos que deben ir en el payload son los del usuario (idnombre) y claims estándar como iat o exp. La clave nunca debe ser uno de ellos.


¡Exactamente! Has entendido perfectamente.

Así es, la clave secreta es una cadena que tú generas o proporcionas. Es un secreto que solo tu servidor (o servidores) debe conocer.

El proceso, tal como lo describes, es:

  1. Tú defines la clave secreta: Es una cadena de texto larga, compleja y aleatoria. Por ejemplo, podría ser algo como: fGh3$kL9!pQrS@vXyZ*2w5z8C&E)H+McQeThWmZq4t7w9z$C&F)J@NcRfUjXn2r5u (nunca uses ejemplos, genera una propia). Esta clave se guarda de forma segura en el entorno de tu servidor.

  2. Construyes el Header y el Payload: Con la información que quieres transmitir (id, nombre, etc.).

  3. Aplicas el algoritmo de firma: Usas una función (generalmente proporcionada por una librería de JWT) que hace exactamente lo que dices:

    • Toma el header codificado en Base64Url.

    • Toma el payload codificado en Base64Url.

    • Los concatena con un punto.

    • Aplica el algoritmo (ej: HS256) junto con tu clave secreta a esa cadena concatenada para generar la firma.

Ejemplo Práctico con Código (Pseudocódigo)

Imagina que tienes estos datos:

javascript
// 1. Tu Clave Secreta (guardada en el servidor)
const secretKey = 'mi-clave-super-secreta-y-compleja-2024';

// 2. Header y Payload
const header = {
  "alg": "HS256",
  "typ": "JWT"
};

const payload = {
  "id": 5421,
  "nombre": "Ana García",
  "clave_de_usuario": "abc123" // ¡Ojo! Esto es la clave DEL USUARIO, no la secreta para firmar.
};

// 3. Una librería de JWT haría esto por ti:
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
const dataToSign = encodedHeader + '.' + encodedPayload;

// 4. Aquí es donde usas tu "secretKey" para crear la firma.
// La función HMACSHA256 es el algoritmo HS256.
const signature = HMACSHA256(dataToSign, secretKey);
const encodedSignature = base64UrlEncode(signature);

// 5. Juntas todo para formar el JWT
const jwtToken = encodedHeader + '.' + encodedPayload + '.' + encodedSignature;
// Resultado: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTQyMSwibm9tYnJlIjoiQW5hIEdhcmPDrWEiLCJjbGF2ZV9kZV91c3VhcmlvIjoiYWJjMTIzIn0.XL4eSmY4UZQQP2fCq7g2a7xRkXeoRkZz7J2lT6p1qQ0

Punto importante: En el ejemplo del payload puse "clave_de_usuario": "abc123". Esto es la contraseña o PIN del usuario en tu sistema, no es la clave secreta para firmar el JWT. Es mala práctica incluir información sensible como contraseñas en el payload, ya que aunque el JWT está firmado, su contenido (payload) es fácilmente decodificable y visible por cualquiera (no está encriptado, solo codificado en Base64).

En resumen: Sí, tu entendimiento es correcto. La clave secreta es externa y se usa junto con el algoritmo para crear la firma, que es la garantía de integridad y autenticidad del token.

Comentarios

Entradas más populares de este blog

1-Autenticación JWT en PHP: Explicación y Ejemplo

8-JWT en 10 minutos - ¿Qué es JWT? ¿Para que sirve? ¿Cuando usarlo? ¿Cómo se usa?

2-JSON Web Tokens