JWT と PASETO のどちらを選ぶかは、柔軟性と誤用への耐性のトレードオフです。JWT(RFC 7519、通常は JWS)では、長いレジストリの中からトークン自身がアルゴリズムを指定できるため、alg: none や RS256 と HS256 の混同といった典型的なバグが起こり得ました。PASETO(Platform-Agnostic SEcurity TOkens)はプロトコルバージョンごとに暗号方式を固定しているため、v4 の public トークンは常に Ed25519、v4 の local トークンは常に XChaCha20 と BLAKE2b の組み合わせです。
比較表
| JWT(JWS) | PASETO v4 | |
|---|---|---|
| 仕様 | RFC 7519 と RFC 7515/7518(IETF の標準) | コミュニティによる仕様(paseto-standard/paseto-spec)。IETF のドラフトは期限切れ |
| 形式 | base64url(header).base64url(claims).base64url(signature) | v4.public. + base64url(メッセージ ‖ 署名)、省略可能な .footer |
| アルゴリズムの選択 | alg ヘッダーで IANA のレジストリから指定 | バージョンと用途で固定 |
| 公開鍵による署名 | ES256、RS256、PS256、EdDSA/Ed25519 など | v4.public:Ed25519 のみ(v3.public:ECDSA P-384) |
| 共通鍵方式のトークン | HS256(MAC のみで、クレームは読める状態)または JWE | v4.local:XChaCha20 + BLAKE2b-MAC(暗号化と認証の両方) |
none とアルゴリズム混同 | 検証者の設定を誤ると起こり得る | そもそも表現できない |
| 暗黙のアサーション(認証されるが送信されない) | なし | あり(v3 と v4) |
| 例のサイズ(後述の実測値) | 227 文字 | 280 文字 |
| 鍵の形式 | JWK と JWKS | PASERK(k4.public…、k4.local…) |
| エコシステム | あらゆる IdP、OAuth/OIDC、ゲートウェイ、クラウドサービス | 多くの言語にライブラリがある。IdP やゲートウェイの対応はわずか |
設計上の違い
JWT の検証者は、アルゴリズムを指定するヘッダーを受け取り、それを信頼してよいかを判断しなければなりません。RFC 7518 には十数種類の署名アルゴリズムと MAC アルゴリズムに加えて none が登録されており、ライブラリはかつてヘッダーの値に従って処理を振り分けていました。これが 2015年に相次いだバグの原因です。署名のないトークンが受け入れられ、RSA の公開鍵が HMAC のシークレットとして使われました(HS256 と RS256 の比較を参照)。RFC 8725 は、最近のライブラリがすべて対応している設定ルールによってこの問題を解決していますが、そのルールはデプロイごとに適用しなければなりません。
PASETO は、ネゴシエーションの代わりにバージョンを使います。各バージョンは用途ごとにプリミティブを 1 つに固定しています。v4 は public トークンに Ed25519 を使い、local トークンには XChaCha20 による暗号化と鍵付き BLAKE2b による MAC を使います。v3 は、NIST のアルゴリズムが求められる環境向けに、それらのアルゴリズム(ECDSA P-384、HMAC-SHA-384 と組み合わせた AES-256-CTR)を使います。v4 の public トークン用に設定した検証者を欺いて、それ以外の方式を受け入れさせることはできません。また鍵には型があるため、local 用の鍵を公開鍵として使うこともできません。事前認証エンコーディング(PAE)は、バージョン、用途、ペイロード、フッター、そして省略可能な暗黙のアサーションを結び付けます。暗黙のアサーションとは、たとえば双方が知っていてもトークンには含まれないテナント識別子のような値です。
実務では、ほかに 2 つの違いが重要です。PASETO の共通鍵方式の local トークンは暗号化されますが、JWT の共通鍵方式の HS256 トークンは MAC が付くだけで、中身は誰でも読めます。v4.local に相当する JWT は JWE です。また、PASETO には JWKS のディスカバリに相当する仕組みがないため、鍵の配布は自分で用意する必要があります。
並べて比較
次のスクリプトは Node.js 26.10 上の paseto 3.1.4 と jose 6.2.13 で実行したもので、1 つの Ed25519 鍵を使い、同じクレームを両方の形式で発行します。
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
ここで PASETO のほうが少し長いのは、ライブラリが iat を追加し、ISO 8601 形式のタイムスタンプを使うためです。JWT は日時を数値で表します。署名のサイズは同じです。JWT 側の algorithms: ["EdDSA"] オプションに注目してください。PASETO では、この 1 行が不要になります。
エコシステムと相互運用性
JWT は、OAuth 2.0(RFC 9068 のアクセストークン)、OpenID Connect の ID トークン、DPoP、SD-JWT、FAPI、クラウドのワークロード ID、そして事実上すべての API ゲートウェイと ID プロバイダーに組み込まれています。ライブラリはあらゆる言語にあり、長年にわたってセキュリティレビューを受けてきました。
PASETO の仕様は、PASETO の標準化プロジェクトが GitHub で管理しています。以前の IETF のインターネットドラフトは、RFC にならないまま期限切れになりました。ライブラリは多くの言語にありますが、品質や対応バージョンはまちまちで、PASETO を受け付けるゲートウェイや ID プロバイダーはほとんどなく、標準のディスカバリの仕組みもありません。したがって PASETO を選ぶということは、発行者、検証者、鍵の配布をすべて自分で担うということです。
どちらを選ぶべきか
- OAuth や OpenID Connect、第三者の利用者、API ゲートウェイ:JWT を使い、鍵ごとにアルゴリズムの許可リストを設定します。
- 自社で管理する、複数の言語で構成されたシステムの内部サービス用トークン:どちらでもかまいません。すべての言語でメンテナンスされている v4 のライブラリがあるなら PASETO を使います。
- クライアントが保存する暗号化トークン(Cookie に入れるセッションデータ):PASETO の
v4.localのほうが JWE より簡単です。JWT を使う場合は、dirまたはA256KWにA256GCMを組み合わせた JWE を使います。 - FIPS の制約がある環境:ES256 または PS256 の JWT か、PASETO v3 を使います。v4 の XChaCha20 と BLAKE2b は FIPS で承認されていません。
- トークンのセキュリティに不慣れなチーム:PASETO では、ある種類の設定エラーがまるごとなくなります。安全なデフォルトを持つ JWT ライブラリ(アルゴリズムが鍵の種類と一致することを求める
joseなど)を使えば、その差は縮まります。
移行時の注意点
- 1 つの検証者で形式を混在させない:プレフィックス(
v4.public.かeyJか)で振り分け、別々の鍵を使う別々の検証コードパスに渡します。 - クレームを明示的に対応付ける:PASETO は
iss、sub、aud、exp、nbf、iat、jti、kidを予約しており、日時は ISO 8601 形式です。JWT は NumericDate(秒)を使います。 - JWKS の代わりに独自の鍵配布を用意します(PASERK 文字列、構成管理、鍵管理サービスなど)。PASETO のフッターに入れた
kidによる鍵のローテーションも含めて設計してください。 - 外部との境界には JWT を残す:外部の相手には JWT を発行し、内部では PASETO を使うシステムも多くあります。
- JWT を使い続けるなら、今すぐ RFC 8725 を適用してください。アルゴリズムを固定し、
typ、iss、audを検証し、256 ビットの HMAC シークレットか非対称鍵を使い、noneを拒否します。