エラーメッセージ
token signature is invalid: crypto/ecdsa: verification error
別の表記
- token signature is invalid: crypto/rsa: verification error
- token signature is invalid: signature is invalid
- token signature is invalid: ed25519: verification error
- crypto/ecdsa: verification error
golang-jwt v4 は内側のテキストだけを返します(例:「crypto/ecdsa: verification error」)。文字列を照合するのではなく、`errors.Is(err, jwt.ErrTokenSignatureInvalid)`(v5)を使ってください。
golang-jwt/jwt (Go) — jwt.ErrTokenSignatureInvalid joined with the algorithm's verification error
このエラーの意味
golang-jwt はトークンを解析し、そのアルゴリズムを受け入れ、`Keyfunc` から鍵を取得しましたが、その鍵では署名を検証できませんでした。v5 のエラーは、`jwt.ErrTokenSignatureInvalid`(「token signature is invalid」)とアルゴリズム固有のエラーを結合したものです。ES* では `crypto/ecdsa: verification error`、RS*/PS* では `crypto/rsa: verification error`、HS* では `signature is invalid` になります。トークンのバイト列と鍵が、単に対応していないということです。
発生する理由
Keyfunc が誤った鍵を返している
よくある別の環境の鍵、ドキュメントに載っている鍵をハードコードしたもの、または `kid` を無視して、複数の鍵を含む鍵セットの最初の鍵を常に返す Keyfunc が原因です。
発行者が署名鍵をローテーションした
よくある起動時に一度だけ公開鍵を読み込むサービスは、鍵のローテーション後も古い鍵で検証し続けます。サービスが鍵を読み込み直すまで、新しいトークンはすべて失敗し、キャッシュされた古いトークンは引き続き通ります。
署名者が DER エンコードの ECDSA 署名を生成した
ときどきgolang-jwt は、ES256 の署名が 64 バイトの生のバイト列(r‖s、RFC 7518)であることを前提としています。KMS や `ecdsa.SignASN1` で署名し、変換せずに使ったトークンには約 71 バイトの DER 署名が入っており、正しい鍵を使っても検証に失敗します。
署名後にトークンが変更された
ときどきペイロードの再エンコード、ヘッダーやペイロードのセグメント内の空白の変更、または 2 つの異なるトークンの一部を組み合わせたトークンが原因です。署名は base64url のテキストを一字一句そのまま対象にしています。
修正方法
- 1.
kid で JWKS から鍵を選択する
Keyfunc には、トークンのヘッダーで指定された鍵を発行者の鍵セットから選ばせ、鍵のローテーション時にはセットを更新させてください(たとえば `github.com/MicahParks/keyfunc` を使います)。`WithValidMethods` によるアルゴリズムの固定は必ず行ってください。
go import ( "context" "github.com/MicahParks/keyfunc/v3" "github.com/golang-jwt/jwt/v5" ) // Resolve the verification key from the issuer's JWKS by the token's kid. k, err := keyfunc.NewDefaultCtx(ctx, []string{"https://issuer.example.com/.well-known/jwks.json"}) if err != nil { return err } token, err := jwt.Parse(raw, k.Keyfunc, jwt.WithValidMethods([]string{"ES256"}), jwt.WithIssuer("https://issuer.example.com/"), jwt.WithAudience("https://api.example.com")) - 2.
両側が同じ鍵を使っていることを確かめる
検証者側にある DER エンコードされた公開鍵と、発行者の秘密鍵に対応する公開鍵を、それぞれハッシュしてください。ハッシュ値が違えば、それ以上調べる必要はありません。
bash # Same key on both sides? Compare SPKI fingerprints. openssl pkey -pubin -in verifier-public.pem -outform DER | sha256sum openssl pkey -in issuer-private.pem -pubout -outform DER | sha256sum - 3.
文字列ではなくエラーの種類で処理する
golang-jwt v5 はセンチネルエラーを公開しています。署名の検証失敗は 401 にマッピングし、ほかの鍵を試したり検証を省略したりするフォールバックは行わないでください。
go switch { case errors.Is(err, jwt.ErrTokenSignatureInvalid): return http.StatusUnauthorized // wrong key or tampered token: never retry with another key blindly case errors.Is(err, jwt.ErrTokenExpired): return http.StatusUnauthorized // client should refresh case errors.Is(err, jwt.ErrTokenMalformed): return http.StatusBadRequest }