エラーメッセージ
token signature is invalid: key is of invalid type: ECDSA verify expects *ecdsa.PublicKey
別の表記
- token signature is invalid: key is of invalid type: HMAC verify expects []byte
- token signature is invalid: key is of invalid type: RSA verify expects *rsa.PublicKey
- key is of invalid type: ECDSA sign expects *ecdsa.PrivateKey
- key is of invalid type
v5 は想定される型を示します(「ECDSA verify expects *ecdsa.PublicKey」「HMAC verify expects []byte」)。v4 は「key is of invalid type」とだけ出力します。v5 はこれを `ErrTokenSignatureInvalid` として報告するため、一見すると署名の検証失敗のように見えます。
golang-jwt/jwt (Go) — jwt.ErrInvalidKeyType
このエラーの意味
`Keyfunc` が返した値(または `SignedString` に渡した鍵)の Go の型が、トークンのアルゴリズムに合っていません。golang-jwt は鍵に対して型アサーションを行います。ES* の検証には `*ecdsa.PublicKey`、RS*/PS* には `*rsa.PublicKey`、EdDSA には `ed25519.PublicKey`、HS* には `[]byte` が必要で、署名には対応する秘密鍵が必要です。暗号処理は一切実行されていません。鍵の値が正しいかどうかを確かめる段階にすら至っておらず、オブジェクトの種類が間違っていたということです。
発生する理由
解析済みの鍵ではなく PEM のバイト列を返している
よくある`.pem` ファイルから読み込んだ `[]byte` を返す方法は、HMAC では機能しますが、非対称アルゴリズムでは機能しません。バイト列は、先に `*ecdsa.PublicKey` や `*rsa.PublicKey` に解析しておく必要があります。
検証に秘密鍵を渡している
よくある`*ecdsa.PublicKey` が必要な箇所では、`*ecdsa.PrivateKey` は受け付けられません。`&priv.PublicKey` を返してください。さらに望ましいのは、検証側に秘密鍵を一切持たせないことです。
HMAC シークレットを string として返している
よくあるHS256 の場合、Keyfunc は `[]byte` を返さなければなりません。`string`(たとえば `os.Getenv` から取得したもの)では、型アサーションに失敗します。
暗号鍵ではなく JWK の構造体やマップを使っている
ときどきJWKS から構造体や `map[string]any` にデコードした鍵は、暗号鍵ではありません。暗号鍵に変換してください(たとえば、go-jose の `JSONWebKey.Key` や、keyfunc の JWKS サポートを使います)。
修正方法
- 1.
PEM を一度だけ型付きの鍵に解析する
golang-jwt には、鍵の種類ごとにヘルパーが用意されています。起動時に解析し、Keyfunc からは型付きの鍵を返してください。
go pub, err := jwt.ParseECPublicKeyFromPEM(pemBytes) // ES256/384/512 // rsaPub, err := jwt.ParseRSAPublicKeyFromPEM(pemBytes) // RS*/PS* // edPub, err := jwt.ParseEdPublicKeyFromPEM(pemBytes) // EdDSA if err != nil { log.Fatal(err) } token, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) { return pub, nil }, jwt.WithValidMethods([]string{"ES256"})) - 2.
HMAC シークレットを []byte として返す
シークレットをバイト列に変換し、`WithValidMethods` で HMAC アルゴリズムに固定してください。こうすれば、非対称の `alg` を持つトークンがそのコードパスに到達することはありません。
go secret := []byte(os.Getenv("JWT_HMAC_SECRET")) // []byte, not string token, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) { return secret, nil }, jwt.WithValidMethods([]string{"HS256"}))