Aller au contenu
ecdsa.com
Node.js · WebCrypto

jose (panva)

jose de Filip Skokan : une implémentation JOSE sans dépendance (JWS, JWE, JWK, JWKS, JWT) reposant sur WebCrypto, qui fonctionne sans modification dans Node.js, les navigateurs, Deno, Bun et les environnements d’exécution edge, avec EdDSA et ML-DSA, mais sans secp256k1.
Licence
MIT
Version vérifiée
6.2.13 ·
Code source
github.com/panva/jose
Documentation
github.com/panva/jose/tree/main/docs

Compatibilité en un coup d’œil

JOSE et JWT

Algorithmes JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519ML-DSA-44ML-DSA-65ML-DSA-87

jose couvre toute la famille JOSE (jetons signés et chiffrés, import et export de JWK, jeux de clés JWK locaux et distants) avec une seule API dans tous les environnements d’exécution JavaScript. Depuis la version 6, toute la cryptographie passe par WebCrypto, si bien que les clés sont des objets CryptoKey (les instances KeyObject de Node.js restent acceptées), et la bibliothèque hérite de la liste de courbes de WebCrypto : ES256, ES384 et ES512, EdDSA et Ed25519, ainsi que, dans la 6.2.13, les algorithmes ML-DSA là où l’environnement d’exécution les fournit. ES256K et Ed448 sont refusés avec ERR_JOSE_NOT_SUPPORTED.

Ses valeurs par défaut sont strictes là où cela compte. jwtVerify() n’accepte jamais alg: none (les jetons non signés ne se lisent qu’au moyen de la classe distincte UnsecuredJWT), et une clé est liée à une seule famille d’algorithmes : un jeton HS256 présenté avec une clé publique EC, ou un jeton RS256 avec une clé EC, échoue avec une TypeError au lieu d’être vérifié. ES256 refuse aussi une clé P-384. Passez tout de même algorithms : avec un jeu de clés qui contient plusieurs types de clés, c’est ce qui empêche un émetteur de changer d’algorithme en silence.

Deux valeurs par défaut tirent en sens contraires. clockTolerance vaut zéro, ce qui est strict, et exp est facultatif, ce qui ne l’est pas : un jeton sans expiration est vérifié, sauf si vous ajoutez ce claim à requiredClaims, comme le fait l’exemple. Un JWS dont la signature ECDSA est arrivée en DER (cas typique d’un jeton assemblé à la main avec un KMS cloud ou openssl dgst) échoue avec le message générique signature verification failed, sans rien qui désigne l’encodage ; le débogueur JWT montre la longueur de la signature d’un coup d’œil, et le convertisseur DER ⇄ brut la corrige.

L’import des clés est explicite : importSPKI et importPKCS8 prennent des chaînes PEM (des octets DER sont refusés avec "spki" must be SPKI formatted string), importX509 prend un certificat PEM et importJWK une JWK. Les fichiers SEC1 EC PRIVATE KEY qu’OpenSSL écrit par défaut sont rejetés par importPKCS8 ; convertissez-les d’abord en PKCS#8, par exemple avec le convertisseur JWK.

Pour vérifier des jetons émis par un fournisseur d’identité, utilisez createRemoteJWKSet avec le jwks_uri du fournisseur : la fonction met les clés en cache, les récupère de nouveau face à un kid inconnu et applique une limitation de débit à ces nouvelles récupérations. Vérifiez ce que le fournisseur publie réellement avec le vérificateur JWKS.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/jose/run.sh a exécuté jose (panva) 6.2.13 sur Node.js 24.22.0 dans l’image de conteneur node:24.22.0-bookworm-slim le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Algorithmes JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519ML-DSA-44ML-DSA-65ML-DSA-87

    Signature et vérification réussies lors des essais avec HS, RS, PS et ES en 256/384/512 bits, EdDSA et Ed25519, ainsi qu’avec ML-DSA-44/65/87 (sous Node.js 24.22) ; ES256K et Ed448 ont levé ERR_JOSE_NOT_SUPPORTED.

    Relevé par la vérification
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA, Ed25519, ML-DSA-44, ML-DSA-65, ML-DSA-87
    evidence jws-algorithms: ES256K: ERR_JOSE_NOT_SUPPORTED: Invalid or unsupported "alg" (Algorithm) value
    evidence jws-algorithms: Ed448: ERR_JOSE_NOT_SUPPORTED: Invalid or unsupported "alg" (Algorithm) value
  • Encodage des signatures ES256

    raw

    Dans les jetons compacts, les signatures ES256 font 64 octets en r‖s brut, la forme qu’exige la RFC 7518.

    Relevé par la vérification
    fact jws-ecdsa-encoding = raw
  • Signature DER dans un JWS

    rejected

    Un jeton dont la signature ES256 a été réencodée en DER échoue avec ERR_JWS_SIGNATURE_VERIFICATION_FAILED, la même erreur qu’une signature falsifiée.

    Relevé par la vérification
    fact jws-der-signature = rejected
    evidence jws-der-signature: ERR_JWS_SIGNATURE_VERIFICATION_FAILED: signature verification failed
  • Liste d’algorithmes autorisés

    from-key

    Sans algorithms, tout algorithme que la clé peut exécuter est autorisé ; un jeton RS256 présenté à une clé EC a échoué avec CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5.

    Relevé par la vérification
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: RS256 token against an EC key: TypeError: CryptoKey does not support this operation, its algorithm.name must be RSASSA-PKCS1-v1_5
  • Jetons non signés (alg none)

    rejected

    jwtVerify() refuse alg: none même quand none figure dans algorithms ; les jetons non sécurisés ne peuvent être décodés qu’avec UnsecuredJWT.decode().

    Relevé par la vérification
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: jwtVerify: ERR_JOSE_NOT_SUPPORTED: alg none is not supported either by JOSE or your javascript runtime
    evidence jwt-alg-none: jwtVerify with algorithms ["none"]: ERR_JOSE_NOT_SUPPORTED: alg none is not supported either by JOSE or your javascript runtime
    evidence jwt-alg-none: UnsecuredJWT.decode: returned sub fact-check
  • Clé publique utilisée comme secret HMAC

    rejected

    Un jeton HS256 dont la clé est le texte PEM de la clé publique échoue face à cette clé publique avec CryptoKey instances must be of type "secret" for the HS256 algorithm, avec ou sans liste d’algorithmes.

    Relevé par la vérification
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: no list: TypeError: CryptoKey instances must be of type "secret" for the HS256 algorithm
    evidence jwt-hmac-public-key: algorithms ["HS256"]: TypeError: CryptoKey instances must be of type "secret" for the HS256 algorithm
  • Courbe vérifiée par rapport à alg

    enforced

    Signer en ES256 avec une clé P-384 échoue avec its algorithm.namedCurve must be P-256.

    Relevé par la vérification
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: TypeError: CryptoKey does not support this operation, its algorithm.namedCurve must be P-256
  • Décalage d’horloge toléré par défaut

    0s

    Aucune marge de tolérance par défaut : un jeton expiré depuis trois secondes a été rejeté ; définissez clockTolerance pour tolérer une dérive d’horloge.

    Relevé par la vérification
    fact jwt-clock-skew = 0s
  • Claim d’expiration obligatoire

    no

    Un jeton sans exp a été vérifié ; ajoutez requiredClaims: ["exp"] pour rendre ce claim obligatoire.

    Relevé par la vérification
    fact jwt-exp-required = no
  • Formats de clé publique

    spki-pemx509jwk

    importSPKI (chaîne PEM), importX509 (certificat PEM) et importJWK ont fonctionné ; des octets DER passés à importSPKI ont été refusés. Les instances KeyObject de Node.js ont aussi été acceptées directement.

    Relevé par la vérification
    fact public-key-formats = spki-pem, x509, jwk
    evidence public-key-formats: spki-der: TypeError: "spki" must be SPKI formatted string
    evidence public-key-formats: KeyObject from node:crypto accepted as a key: true
  • Formats de clé privée

    pkcs8-pemjwk

    importPKCS8 prend une chaîne PEM PKCS#8 et importJWK une JWK avec d ; un PEM SEC1 EC PRIVATE KEY a été refusé avec "pkcs8" must be PKCS#8 formatted string.

    Relevé par la vérification
    fact private-key-formats = pkcs8-pem, jwk
    evidence private-key-formats: sec1-pem: TypeError: "pkcs8" must be PKCS#8 formatted string

Utilisation

Émettre un jeton ES256 avec un identifiant de clé, puis le vérifier via un jeu de clés JWK en exigeant l’algorithme, l’émetteur, l’audience et l’expiration.
import * as jose from "jose";

const { privateKey, publicKey } = await jose.generateKeyPair("ES256");
const jwk = { ...(await jose.exportJWK(publicKey)), kid: "2026-10", alg: "ES256" };

const token = await new jose.SignJWT({ sub: "user-42" })
  .setProtectedHeader({ alg: "ES256", kid: jwk.kid })
  .setIssuer("https://auth.example.com")
  .setAudience("api")
  .setExpirationTime("10m")
  .sign(privateKey);

// The key set picks the key by kid; createRemoteJWKSet(new URL(...)) does the same over HTTPS.
const keys = jose.createLocalJWKSet({ keys: [jwk] });
const { payload } = await jose.jwtVerify(token, keys, {
  algorithms: ["ES256"],
  issuer: "https://auth.example.com",
  audience: "api",
  requiredClaims: ["exp"],
});

console.log("sub:", payload.sub);

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur node:24.22.0-bookworm-slim, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
npm init -y >/dev/null && npm install --no-audit --no-fund jose@6.2.13
Commande
node usage.mjs
La sortie doit correspondre à cette expression régulière
sub: user-42

Bibliothèques