hwtr-js

Implémentation de référence JavaScript du protocole HWT github. Démos utilisant cette implémentation sur hwt-demo.

documentation officielle issues

Format du token : hwt.signature.key-id.expires-unix-seconds.format.payload


Installation

// 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'

Démarrage rapide

// Générer les clés une fois — stocker le résultat de façon sécurisée
const keyConfig = await Hwtr.generateKeys({ type: 'Ed25519' })

// Créer une instance
const hwtr = await Hwtr.factory({}, keyConfig)

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

// Vérifier
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>

Constructeur privilégié. Équivalent à new Hwtr(options).importKeys(keyConfig).

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

Options du constructeur — toutes optionnelles :

Option Défaut Description
expiresInSeconds 60 Durée de vie du token par défaut
maxTokenLifetimeSeconds 86400 Plafond absolu ; 0 = illimité
leewaySeconds 1 Tolérance au décalage d'horloge. La spécification recommande de ne pas dépasser 5 minutes (300s) ; aucun maximum imposé.
maxTokenSizeBytes 4096 Plage : 512–16384
format 'j' Codec du payload
signatureSize 0 Tronquer la signature HMAC à N caractères ; 0 = complète ; ignoré pour les clés asymétriques
throwOnInvalid false Lever une exception plutôt que retourner ok: false
throwOnExpired false Lever une exception plutôt que retourner ok: false
throwOnGenerate true Lever une exception en cas d'échec de signature
throwOnEncoding true Lever une exception en cas d'échec du codec

Génération de clés

// Générer les clés une fois — stocker le résultat de façon sécurisée
const keyConfig = await Hwtr.generateKeys({
  count: 2,           // nombre de clés (défaut : 1)
  current: 'primary', // id de la clé de signature
  type: 'Ed25519',    // algorithme (défaut : 'HMAC')
})
// → { current, type, keys: [{ id, created, privateKey, publicKey }] }

// Clé HMAC unique
const key = Hwtr.generateKey({ id: 'main' })
// → { id, secret, created }

// Paire asymétrique unique
const pair = await Hwtr.generateKeyPair({ id: 'k1', type: 'ECDSA-P256' })
// → { id, created, publicKey, privateKey }  (base64url SPKI/PKCS8)

Algorithmes supportés : 'HMAC', 'Ed25519', 'ECDSA-P256', 'ECDSA-P384', 'ECDSA-P521'

HMAC est symétrique — mono-service uniquement. Utiliser des types asymétriques pour la vérification inter-services. Le support de P-521 varie selon l'environnement ; tester avant utilisation.


hwtr.importKeys(keyConfig)Promise<Hwtr>

Importe les clés de signature et/ou de vérification. factory() l'appelle automatiquement.

// structure de keyConfig
{
  current: 'primary',    // id de la clé utilisée pour signer les nouveaux tokens
  type: 'Ed25519',
  keys: [
    { id: 'primary', created: '...', privateKey: 'BASE64URL', publicKey: 'BASE64URL' }
    // HMAC: { id, created, secret }
  ],
  publicKeys: {          // optionnel : clés de vérification uniquement, provenant d'autres services
    'partner-key': 'BASE64URL'
  }
}

Plusieurs clés sont supportées pour la rotation. Toute clé dont l'id correspond au kid d'un token peut le vérifier. Seule la clé avec id === current signe les nouveaux tokens.


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

Crée un token avec l'expiration par défaut.

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

// hiddenData est signé mais non intégré — le vérificateur (verifier) doit fournir la même valeur
const token = await hwtr.create({ sub: 'user:123' }, { ip: '203.0.113.1' })

Retourne '' (ou lève une exception si throwOnInvalid) lorsque le token dépasserait maxTokenSizeBytes.


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

Crée un token avec une durée de vie spécifique. Lève une exception si elle dépasse maxTokenLifetimeSeconds.

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

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

Vérifie la signature et l'expiration. Ne lève pas d'exception par défaut.

const result = await hwtr.verify(token)
// result.ok      — true si valide
// result.data    — payload décodé
// result.expired — true si expiré
// result.expires — unix seconds
// result.error   — présent en cas d'échec

Valeurs 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? }>

Décode le payload sans vérifier la signature ni l'expiration. À des fins d'inspection uniquement.


Vérification inter-services

Un service qui ne fait que vérifier (sans signer) passe publicKeys sans keys privées :

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

Méthodes utilitaires pour la distribution de clés :

const pub = await hwtr.exportPublicKey('primary')       // base64url SPKI string
const all = await hwtr.getPublicKeys()                  // { id: base64url, ... }
await hwtr.addPublicKey({ id, publicKeyBase64, type })  // ajouter une clé externe à l'exécution

Codecs

'j' (JSON) est le seul codec intégré. Enregistrer les codecs personnalisés une fois par processus :

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

Noms de format : /^[a-zA-Z][a-zA-Z0-9]{1,19}$/. Les noms déjà enregistrés lèvent une exception.

Un codec JSON étendu (jx) supportant Date, BigInt, Map, Set et les tableaux typés est disponible dans le dépôt source sous hwtr.formats.js — il n'est pas inclus dans le package publié.


Utilitaires

Hwtr.timingSafeEqual(a, b)      // comparaison en temps constant ; chaînes ou Uint8Array
Hwtr.bufferToBase64Url(buffer)  // ArrayBuffer | Uint8Array → base64url string
Hwtr.base64urlToUint8Array(str) // base64url → Uint8Array ; tableau vide en cas d'entrée invalide
Hwtr.textToBase64Url(text)      // UTF-8 string → base64url
Hwtr.base64urlToText(str)       // base64url → UTF-8 string
Hwtr.isHwt(str)                 // true si str contient un segment de signature HWT plausible
Hwtr.ALGORITHMS                 // carte des algorithmes supportés
Hwtr.version                    // version

Tous également exportés en tant qu'exports nommés depuis hwtr.js.


Liens connexes


Licence

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

Licence Apache 2.0. Voir LICENSE.