Choisir entre JWT et PASETO, c’est arbitrer entre souplesse et résistance aux mauvais usages. JWT (RFC 7519, le plus souvent sous forme de JWS) laisse le jeton indiquer son algorithme, choisi dans un long registre, ce qui a permis des bugs classiques comme alg: none et la confusion entre RS256 et HS256. PASETO (Platform-Agnostic SEcurity TOkens) fixe la cryptographie par version du protocole : un jeton v4 public utilise donc toujours Ed25519, et un jeton v4 local toujours XChaCha20 avec BLAKE2b.
Vue d’ensemble
| JWT (JWS) | PASETO v4 | |
|---|---|---|
| Spécification | RFC 7519 avec les RFC 7515/7518 (standards IETF) | Spécification communautaire (paseto-standard/paseto-spec) ; Internet-Drafts IETF expirés |
| Format | base64url(header).base64url(claims).base64url(signature) | v4.public. + base64url(message ‖ signature), .footer facultatif |
| Choix de l’algorithme | En-tête alg issu du registre IANA | Fixé par la version et l’usage |
| Signature à clé publique | ES256, RS256, PS256, EdDSA/Ed25519 et d’autres | v4.public : Ed25519 uniquement (v3.public : ECDSA P-384) |
| Jetons symétriques | HS256 (MAC seul, claims lisibles) ou JWE | v4.local : XChaCha20 + BLAKE2b-MAC (chiffré et authentifié) |
none et confusion d’algorithme | Possibles si le vérificateur est mal configuré | Impossibles à exprimer |
| Assertions implicites (authentifiées, non transmises) | Non | Oui (v3 et v4) |
| Taille de l’exemple (mesurée plus bas) | 227 caractères | 280 caractères |
| Format de clé | JWK et JWKS | PASERK (k4.public…, k4.local…) |
| Écosystème | Tous les IdP, OAuth/OIDC, passerelles, services cloud | Bibliothèques dans de nombreux langages ; peu de prise en charge par les IdP et les passerelles |
Différences de conception
Un vérificateur de JWT voit un en-tête qui désigne l’algorithme et doit décider s’il lui fait confiance. La RFC 7518 enregistre plus d’une douzaine d’algorithmes de signature et de MAC, auxquels s’ajoute none, et les bibliothèques choisissaient historiquement leur traitement d’après l’en-tête, ce qui a produit la vague de bugs de 2015 : jetons non signés acceptés, et clés publiques RSA utilisées comme secrets HMAC (voir HS256 vs RS256). La RFC 8725 corrige cela par des règles de configuration que toutes les bibliothèques modernes prennent en charge, mais chaque déploiement doit les appliquer.
PASETO remplace la négociation par des versions. Chaque version fixe une primitive par usage : v4 utilise Ed25519 pour les jetons public, et le chiffrement XChaCha20 avec un MAC BLAKE2b à clé pour les jetons local ; v3 utilise des algorithmes du NIST (ECDSA P-384, AES-256-CTR avec HMAC-SHA-384) pour les environnements qui en ont besoin. Un vérificateur configuré pour les jetons v4 public ne peut pas être amené par la ruse à accepter autre chose, et les clés sont typées, si bien qu’une clé locale ne peut pas servir de clé publique. L’encodage de pré-authentification (PAE) lie la version, l’usage, la charge utile, le pied de page (footer) et d’éventuelles assertions implicites, comme un identifiant de locataire que les deux parties connaissent mais que le jeton ne transporte pas.
Deux autres différences comptent en pratique. Les jetons symétriques local de PASETO sont chiffrés, alors que les jetons symétriques HS256 des JWT sont seulement protégés par un MAC et restent lisibles ; l’équivalent JWT de v4.local est un JWE. Et PASETO n’a pas d’équivalent à la découverte des clés via JWKS : la distribution des clés vous incombe.
Côte à côte
Le script ci-dessous, exécuté avec Node.js 26.10, paseto 3.1.4 et jose 6.2.13, émet les mêmes claims avec une seule clé Ed25519 dans les deux formats.
import { generateKeyPairSync } from "node:crypto";
import { SignJWT, jwtVerify } from "jose";
import { V4 } from "paseto";
const claims = { sub: "user-42", scope: "orders:read" };
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
// PASETO v4.public: Ed25519 is implied by the version; no alg header exists.
const paseto = await V4.sign(claims, privateKey, { expiresIn: "15m", issuer: "https://auth.example.com" });
const pasetoClaims = await V4.verify(paseto, publicKey, { issuer: "https://auth.example.com" });
// JWT with the same key: the verifier must pin the algorithm itself.
const jwt = await new SignJWT(claims).setProtectedHeader({ alg: "EdDSA" }).setIssuer("https://auth.example.com").setExpirationTime("15m").sign(privateKey);
const { payload } = await jwtVerify(jwt, publicKey, { algorithms: ["EdDSA"], issuer: "https://auth.example.com" });
console.log("PASETO prefix:", paseto.slice(0, 10), "length:", paseto.length, "sub:", pasetoClaims.sub);
console.log("JWT prefix:", jwt.slice(0, 10), "length:", jwt.length, "sub:", payload.sub);
PASETO prefix: v4.public. length: 280 sub: user-42
JWT prefix: eyJhbGciOi length: 227 sub: user-42
Le jeton PASETO est un peu plus long ici, car la bibliothèque ajoute iat et utilise des horodatages ISO 8601, alors que le JWT utilise des dates numériques ; les signatures ont la même taille. Notez l’option algorithms: ["EdDSA"] côté JWT : c’est précisément cette ligne que PASETO rend inutile.
Écosystème et interopérabilité
JWT est intégré à OAuth 2.0 (jetons d’accès RFC 9068), aux jetons d’ID OpenID Connect, à DPoP, à SD-JWT, à FAPI, à l’identité des charges de travail dans le cloud et à pratiquement toutes les passerelles d’API et tous les fournisseurs d’identité. Des bibliothèques existent pour tous les langages, avec des années de revue de sécurité.
La spécification de PASETO est maintenue sur GitHub par le projet PASETO Standard ; les Internet-Drafts antérieurs de l’IETF ont expiré sans devenir des RFC. Des bibliothèques existent pour de nombreux langages, mais leur qualité et les versions qu’elles couvrent varient, les passerelles et les fournisseurs d’identité acceptent rarement PASETO, et il n’existe aucun mécanisme de découverte normalisé. Choisir PASETO signifie donc que vous gérez vous-même l’émetteur, les vérificateurs et la distribution des clés.
Quand choisir l’un ou l’autre
- OAuth ou OpenID Connect, consommateurs tiers, passerelles d’API : JWT, avec une liste d’autorisation d’algorithmes par clé.
- Jetons de service internes dans un système polyglotte que vous contrôlez : l’un ou l’autre ; PASETO si chaque langage dispose d’une bibliothèque v4 maintenue.
- Jetons chiffrés stockés par les clients (données de session dans un cookie) : PASETO
v4.localest plus simple que JWE ; avec JWT, utilisez JWE avecdirouA256KWetA256GCM. - Environnements soumis aux contraintes FIPS : JWT avec ES256 ou PS256, ou PASETO v3 ; XChaCha20 et BLAKE2b, qu’utilise v4, ne sont pas approuvés FIPS.
- Équipes novices en sécurité des jetons : PASETO élimine toute une classe d’erreurs de configuration ; une bibliothèque JWT aux paramètres par défaut sûrs (comme
jose, qui exige que les algorithmes correspondent aux types de clés) réduit l’écart.
Notes de migration
- Ne mélangez pas les formats derrière un même vérificateur : aiguillez selon le préfixe (
v4.public.oueyJ) vers des chemins de vérification distincts, avec des clés distinctes. - Faites correspondre les claims explicitement : PASETO réserve
iss,sub,aud,exp,nbf,iat,jtietkid, avec des dates ISO 8601 ; JWT utilise des secondes NumericDate. - Remplacez JWKS par votre propre distribution des clés (chaînes PASERK, gestion de configuration ou service de clés), y compris la rotation par
kiddans le pied de page PASETO. - Gardez JWT en périphérie : de nombreux systèmes émettent des JWT pour les parties externes et utilisent PASETO en interne.
- Si vous restez sur JWT, appliquez la RFC 8725 dès maintenant : imposez les algorithmes, validez
typ,issetaud, utilisez des secrets HMAC de 256 bits ou des clés asymétriques, et rejeteznone.