エラーメッセージ
invalid signature
このエラーの意味
トークンは問題なく解析でき、構造も有効ですが、暗号学的なチェックに失敗しました。header.payload に対する署名を、指定した鍵で検証できません。その鍵がトークンに署名した鍵ではないか、署名されたバイト列が署名後に変更されたかのどちらかです。
発生する理由
誤った鍵で検証している
よくある圧倒的に多いケースです。環境にある鍵が、発行者が使った鍵と異なっています。鍵のローテーション後の古い公開鍵、本番環境のトークンに対するステージング環境の鍵、あるいは検証者が 1 つの鍵に固定しているのに発行者が別の鍵で署名している場合の JWKS の kid の不一致などです。
自作の署名処理が誤ったバイト列に署名した
ときどきトークンを手作業で組み立てるコードは、base64url(header).base64url(payload) の ASCII バイト列に SHA-256 で署名し、RFC 7518 が ES256 に求める 64 バイトの生の r‖s を出力しなければなりません。JSON そのもの、標準の base64 のセグメント、または ES256 ヘッダーのもとで SHA-384 のダイジェストに署名すると、形式は正しくても決して検証に成功しない署名ができます。(DER エンコードの署名はチェックの前に「"ES256" signatures must be "64" bytes」で拒否され、環境変数によって壊れた PEM はそもそも鍵として受け付けられず、「secretOrPublicKey must be an asymmetric key when using ES256」で失敗します。)
署名後にトークンが変更された
ときどきプロキシ、ロガー、クライアントのいずれかが、ペイロードの JSON を再シリアライズしたか、その中の文字を変更しています。ヘッダーやペイロードの文字が 1 文字変わるだけで署名は無効になります。それこそが署名の目的です。(セグメントに Base64 のパディングが追加された場合は 1 つ手前の段階で「invalid token」で失敗し、途中で切れたトークンは「jwt malformed」または署名の長さのエラーで失敗します。)
修正方法
- 1.
両側で鍵のフィンガープリントを取る
検証者が実際に読み込んだ公開鍵をハッシュし、発行者側の公開鍵のハッシュと比較してください。フィンガープリントが異なれば、圧倒的に多い原因である古い鍵または誤った鍵ということになり、一度で原因の特定が終わります。
js import { createHash, createPublicKey } from "node:crypto"; // Run on the verifier, and on the issuer with its signing key: the outputs must match. const spki = createPublicKey(process.env.JWT_PUBLIC_KEY).export({ type: "spki", format: "der" }); console.log(createHash("sha256").update(spki).digest("hex")); - 2.
トークンの kid と検証用の鍵を照合する
検証せずにヘッダーをデコードし、その kid を手元の鍵と比較してください。発行者が JWKS を公開している場合は、鍵をハードコードするのではなく、kid で鍵を選んでください。
js const { header } = jwt.decode(token, { complete: true }); console.log(header.kid, header.alg); // compare with your JWKS / configured key - 3.
デバッガーで不一致の種類を特定する
トークンと公開鍵を JWT デバッガーに貼り付けてください。ES256/ES384/ES512 をローカルで検証し、単なる真偽値ではなく、鍵の誤り、ペイロードの改ざん、署名のエンコーディングの不備を区別して示します。