JWS y JWT — formato estándar de firmas
JWS —JSON Web Signature— es el estándar IETF (RFC 7515) que define cómo se firma una payload JSON. JWT —JSON Web Token— es una aplicación específica de JWS donde la payload es un conjunto de claims sobre una entidad (típicamente un usuario).
Son las primitivas más usadas en internet moderno: OAuth 2.0, OpenID Connect, casi cualquier sistema de auth web, y la base de SD-JWT VC para credenciales verificables.
La estructura de un JWS / JWT
Un JWT es una cadena de tres partes separadas por puntos:
<base64url(header)>.<base64url(payload)>.<base64url(signature)>
Ejemplo concreto:
eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDp3ZWI6c2FsdGEuZ29iLmFyI2tleS0xIn0
.
eyJpc3MiOiJkaWQ6d2ViOnNhbHRhLmdvYi5hciIsInN1YiI6ImRpZDprZXk6ejZN
ay4uLiIsImlhdCI6MTcxNDUyMTYwMCwiZXhwIjoxNzQ2MDU3NjAwfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Cada parte es base64url-encoded para que el JWT sea seguro de transmitir en URLs y headers HTTP.
El header
El header contiene metadata sobre el JWT mismo:
{
"alg": "EdDSA",
"typ": "JWT",
"kid": "did:web:salta.gob.ar#key-1"
}
algindica el algoritmo de firma usado (EdDSA, ES256, RS256, etc.).typindica el tipo (JWT, o variantes como vc+sd-jwt para SD-JWT VC).kididentifica qué clave del firmante se usó.
El payload
El payload contiene los claims (datos) del JWT:
{
"iss": "did:web:salta.gob.ar",
"sub": "did:key:z6Mk...",
"iat": 1714521600,
"exp": 1746057600,
"nombre": "Juan Pérez",
"domicilio": "Av. Belgrano 1234"
}
Hay claims estándar (registrados en IANA) y claims custom. Los estándar incluyen:
| Claim | Significado |
|---|---|
iss | Issuer (quien firma) |
sub | Subject (sobre quién es) |
aud | Audience (a quién está dirigido) |
iat | Issued At (timestamp emisión) |
exp | Expiration time (timestamp vencimiento) |
nbf | Not Before (timestamp de inicio de validez) |
jti | JWT ID (identificador único) |
La firma
La firma se calcula firmando base64url(header) + "." + base64url(payload) con la clave privada del firmante usando el algoritmo del header.
Para verificar:
- Decodificar header para saber qué algoritmo se usó.
- Identificar la clave pública del firmante (vía
kido vía DID resolution). - Verificar la firma con la clave pública.
Por qué JWT se volvió ubicuo
Cinco razones que explican la adopción masiva de JWT en internet:
Self-contained
Un JWT contiene todos los datos necesarios para validar. No requiere consultar al issuer.
Stateless
El servidor no necesita guardar sesiones. El JWT mismo es la sesión.
Multi-format friendly
Base64url + JSON es seguro en URLs, headers HTTP, deep links, QR codes.
Standardizado
RFC 7515 + RFC 7519 son estables desde 2015. Implementaciones en cada lenguaje.
Extensible
Claims custom se pueden agregar sin romper compatibilidad.
Los problemas conocidos
A pesar de su éxito, JWT tiene problemas conocidos:
JWT vs SD-JWT VC
JWT clásico no soporta selective disclosure. SD-JWT VC es la extensión que lo agrega:
| JWT clásico | SD-JWT VC | |
|---|---|---|
| Estructura | header.payload.signature | header.payload.signature |
| Selective disclosure | No | Sí, nativo |
| Compatibilidad OAuth | Sí | Sí |
| Compatible W3C VC | Limitada | Completa |
Para credenciales verificables modernas, SD-JWT VC es el formato. Pero JWT clásico sigue siendo válido para tokens de acceso y otros casos sin necesidad de selective disclosure.
Implementación
En código TypeScript/Node:
import { SignJWT, jwtVerify } from "jose";
// Firmar
const jwt = await new SignJWT({ nombre: "Juan", domicilio: "..." })
.setProtectedHeader({ alg: "EdDSA", kid: "did:web:salta.gob.ar#key-1" })
.setIssuer("did:web:salta.gob.ar")
.setIssuedAt()
.setExpirationTime("1y")
.sign(privateKey);
// Verificar
const { payload } = await jwtVerify(jwt, publicKey, {
issuer: "did:web:salta.gob.ar",
});
Librerías equivalentes existen en Python (PyJWT), Java (jjwt), Go (golang-jwt), Rust (jsonwebtoken).
Referencias
Relacionados
- Firmas digitales en 5 minutos — el concepto base
- SD-JWT VC — el formato preferido de Sovra — la extensión para credenciales verificables
- SD-JWT — cómo funciona la divulgación selectiva — cómo funciona SD-JWT internamente

