hwtr-js

Implementación de referencia en JavaScript del protocolo HWT github. Demostraciones usando esta biblioteca en hwt-demo github.

documentación canónica issues

Formato del token: hwt.signature.key-id.expires-unix-seconds.format.payload


Instalación

// Deno / JSR
import Hwtr from 'jsr:@hwt/hwtr-js'

// Node / Bun via JSR
// npx jsr add @hwt/hwtr-js

// Local
import Hwtr from './hwtr.js'

Inicio rápido

// Genera las claves una vez — guarda el resultado de forma segura
const keyConfig = await Hwtr.generateKeys({ type: 'Ed25519' })

// Crea una instancia
const hwtr = await Hwtr.factory({}, keyConfig)

// Firma
const token = await hwtr.create({ sub: 'user:123', role: 'editor' })

// Verifica
const result = await hwtr.verify(token)
if (result.ok) {
  console.log(result.data) // { sub: 'user:123', role: 'editor' }
}

API

Hwtr.factory(options, keyConfig)Promise<Hwtr>

Constructor preferido. Equivalente a new Hwtr(options).importKeys(keyConfig).

const hwtr = await Hwtr.factory({ expiresInSeconds: 3600 }, keyConfig)

Opciones del constructor — todas opcionales:

Opción Predeterminado Descripción
expiresInSeconds 60 Vida útil predeterminada del token
maxTokenLifetimeSeconds 86400 Límite máximo; 0 = ilimitado
leewaySeconds 1 Tolerancia de desfase de reloj. La especificación recomienda no superar 5 minutos (300 s); sin máximo impuesto.
maxTokenSizeBytes 4096 Rango: 512–16384
format 'j' Codec del payload
signatureSize 0 Truncar la firma HMAC a N caracteres; 0 = completa; ignorado para claves asimétricas
throwOnInvalid false Lanzar excepción en lugar de devolver ok: false
throwOnExpired false Lanzar excepción en lugar de devolver ok: false
throwOnGenerate true Lanzar excepción ante fallo de firma
throwOnEncoding true Lanzar excepción ante fallo de codec

Generación de claves

// Genera las claves una vez — pásalas directamente a factory o importKeys
const keyConfig = await Hwtr.generateKeys({
  count: 2,           // número de claves (predeterminado: 1)
  current: 'primary', // id de la clave de firma
  type: 'Ed25519',    // algoritmo (predeterminado: 'HMAC')
})
// → { current, type, keys: [{ id, created, privateKey, publicKey }] }

// Clave HMAC única
const key = Hwtr.generateKey({ id: 'main' })
// → { id, secret, created }

// Par asimétrico único
const pair = await Hwtr.generateKeyPair({ id: 'k1', type: 'ECDSA-P256' })
// → { id, created, publicKey, privateKey }  (base64url SPKI/PKCS8)

Algoritmos soportados: 'HMAC', 'Ed25519', 'ECDSA-P256', 'ECDSA-P384', 'ECDSA-P521'

HMAC es simétrico — solo para un único servicio. Usa tipos asimétricos para verificación entre servicios. El soporte de P-521 varía según el entorno; prueba antes de usar.


hwtr.importKeys(keyConfig)Promise<Hwtr>

Importa claves de firma y/o verificación. factory() hace esto por ti.

// estructura de keyConfig
{
  current: 'primary',    // id de la clave usada para firmar nuevos tokens
  type: 'Ed25519',
  keys: [
    { id: 'primary', created: '...', privateKey: 'BASE64URL', publicKey: 'BASE64URL' }
    // HMAC: { id, created, secret }
  ],
  publicKeys: {          // opcional: claves de solo verificación de otros servicios
    'partner-key': 'BASE64URL'
  }
}

Se admiten múltiples claves para rotación. Cualquier clave cuyo id coincida con el kid de un token puede verificarlo. Solo la clave con id === current firma nuevos tokens.


hwtr.create(payload, hiddenData?)Promise<string>

Crea un token con la expiración predeterminada.

const token = await hwtr.create({ sub: 'user:123' })

// hiddenData está firmado pero no incorporado — el verifier debe suministrar el mismo valor
const token = await hwtr.create({ sub: 'user:123' }, { ip: '203.0.113.1' })

Devuelve '' (o lanza excepción si throwOnInvalid) cuando el token superaría maxTokenSizeBytes.


hwtr.createWith(expiresInSeconds, payload, hiddenData?)Promise<string>

Crea un token con una vida útil específica. Lanza excepción si supera maxTokenLifetimeSeconds.

const token = await hwtr.createWith(300, { sub: 'user:123', oneTime: true })

hwtr.verify(token, hiddenData?)Promise<VerifyResult>

Verifica la firma y la expiración. No lanza excepción por defecto.

const result = await hwtr.verify(token)
// result.ok      — true si es válido
// result.data    — payload decodificado
// result.expired — true si venció
// result.expires — segundos Unix
// result.error   — presente ante cualquier fallo

Valores de result.error: 'hwt invalid', 'hwt invalid format', 'hwt expired', 'hwt unknown encoding "x"', 'hwt unknown key', 'hwt invalid signature', 'hwt data decoding failed'


hwtr.decode(token)Promise<{ data, expires, error? }>

Decodifica el payload sin verificar la firma ni la expiración. Solo para inspección.


Verificación entre servicios

Un servicio que solo verifica (sin firma) pasa publicKeys sin keys privadas:

const verifyOnly = await Hwtr.factory({}, {
  type: 'Ed25519',
  keys: [],
  publicKeys: { 'auth-key': 'BASE64URL' }
})

Métodos de conveniencia para distribución de claves:

const pub = await hwtr.exportPublicKey('primary')       // base64url SPKI string
const all = await hwtr.getPublicKeys()                  // { id: base64url, ... }
await hwtr.addPublicKey({ id, publicKeyBase64, type })  // agrega una clave externa en tiempo de ejecución

Codecs

'j' (JSON) es el único codec integrado. Registra codecs personalizados una vez por proceso:

Hwtr.registerFormat('name', {
  encode(data) { /* → Uint8Array */ },
  decode(buffer) { /* → value */ }
})
Hwtr.formats // → ['j', 'name', ...]

Nombres de formato: /^[a-zA-Z][a-zA-Z0-9]{1,19}$/. Los nombres ya registrados lanzan excepción.

Un codec JSON extendido (jx) compatible con Date, BigInt, Map, Set y arrays tipados está disponible en el repositorio fuente en hwtr.formats.js — no está incluido en el paquete publicado.


Utilidades

Hwtr.timingSafeEqual(a, b)      // comparación en tiempo constante; cadenas o Uint8Array
Hwtr.bufferToBase64Url(buffer)  // ArrayBuffer | Uint8Array → base64url string
Hwtr.base64urlToUint8Array(str) // base64url → Uint8Array; arreglo vacío si la entrada es inválida
Hwtr.textToBase64Url(text)      // UTF-8 string → base64url
Hwtr.base64urlToText(str)       // base64url → UTF-8 string
Hwtr.isHwt(str)                 // true si str contiene un segmento de firma HWT válido
Hwtr.ALGORITHMS                 // mapa de algoritmos soportados
Hwtr.version                    // versión

Todos también se exportan como exportaciones nombradas desde hwtr.js.


Relacionado


Licencia

Copyright 2026 Jim Montgomery
SPDX-License-Identifier: Apache-2.0

Apache License 2.0. Ver LICENSE.