JWTの署名検証に失敗する原因と対処
JsonWebTokenError: invalid signature やsignature verification failed は JWT を扱うサーバで最も多いエラーの一つです。 原因は 5 つに大別でき、切り分け順さえ決めれば短時間で特定できます。
invalid signature and signature verification failed are the most common JWT errors on the server side. There are five typical causes; a fixed diagnostic order gets you to the root quickly.
TL;DR
- 鍵不一致 / key mismatch が最頻出(発行側と検証側で別の鍵を使っている)
alg不一致 / algorithm mismatch(HS256 と RS256 を混同)- JWKS の
kidが解決できない / key rotation failure aud,iss,exp,nbfの検証失敗- トークンの文字列が途中で壊れた(URLエンコード2回・空白混入)
1. 切り分けの順序 / Diagnostic order
- トークンを JWT Decoder で開く(payload と header を確認)
- header の
algと検証側の allow-list を比較 - header の
kidを JWKS エンドポイントで解決できるか確認 iss,audが想定値かexp,nbfの時刻とサーバ時計の差(clock skew)
2. 原因別パターン / Cause table
| 症状 / Symptom | 原因 / Cause | 対処 / Fix |
|---|---|---|
invalid signature | 鍵不一致 / Key mismatch | 発行側と検証側で同じ secret / public key を使う |
invalid algorithm | alg mismatch | algorithms: ['RS256'] で allow-list 固定 |
unable to find a signing key that matches 'kid' | JWKS 未更新 / key rotation | JWKS キャッシュをflush。発行側と検証側の期間を確認 |
jwt audience invalid | aud 不一致 | 検証側の audience オプションを実値と合わせる |
jwt expired | exp切れ / clock skew | NTPで時刻同期、clockTolerance: 5 |
invalid token (3分割失敗) | 文字列破損 / Base64URL崩れ | URLエンコード2重化、Bearer プレフィクスの削除漏れを確認 |
3. 言語別の検証コード / Verification in each language
Node.js (jsonwebtoken)
import jwt from "jsonwebtoken";
const payload = jwt.verify(token, publicKey, {
algorithms: ["RS256"], // allow-list 必須
issuer: "https://example.com",
audience: "api.example.com",
clockTolerance: 5,
});Python (PyJWT)
import jwt
payload = jwt.decode(
token, public_key,
algorithms=["RS256"],
issuer="https://example.com",
audience="api.example.com",
leeway=5,
)Go (github.com/golang-jwt/jwt)
tok, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected alg")
}
return publicKey, nil
})4. セキュリティ上の注意 / Security notes
- alg: none を許可しない。allow-list で明示拒否
- HS256 と RS256 の混在禁止。公開鍵を HMAC secret として渡すと署名偽造される
- 検証前に
decodeだけで使わない。必ずverifyを通す
5. JWKSキャッシュと鍵ローテーションのタイミング問題 / JWKS caching and key rotation timing
JWKSエンドポイントのレスポンスは、毎回の検証で取得し直すとレイテンシが増えるため、 多くのクライアントライブラリが一定時間キャッシュします。鍵をローテーションした直後は、 新しいkidで署名されたトークンが届いても、検証側のキャッシュがまだ 古いJWKSを保持していてkidを解決できない、という失敗が起こり得ます。
- 鍵ローテーション時は、しばらく新旧両方の鍵をJWKSに公開しておく
- 検証側は
kidが見つからない場合にJWKSを再取得するフォールバックを持たせる (多くの主要ライブラリは標準でこの挙動を持つ) - 古い鍵をJWKSから削除するのは、その鍵で署名されたトークンが全て失効した後にする
6. 署名対象のバイト列が一致しているか確認する / Verifying the exact signed bytes
JWTの署名はbase64url(header) + "." + base64url(payload)という受け取った文字列そのものに対して計算されています。自前のJWT実装で、 headerやpayloadを一度オブジェクトへデコードしてから再エンコードし、 その再エンコード結果に対して署名検証をしてしまうと、JSONキーの並び順や空白の違いだけで バイト列が変わり、改ざんが無くても署名不一致になります。
// 危険な実装: デコード→オブジェクト化→再エンコードしてから検証
const [h, p, s] = token.split(".");
const header = JSON.parse(base64UrlDecode(h));
const payload = JSON.parse(base64UrlDecode(p));
const reEncoded = base64UrlEncode(JSON.stringify(header)) + "." + base64UrlEncode(JSON.stringify(payload));
verify(reEncoded, s); // 元のバイト列と一致しない可能性がある
// 正しい実装: 受け取った生のheader.payload文字列に対して検証する
const [h2, p2, s2] = token.split(".");
verify(`${h2}.${p2}`, s2);標準的なJWTライブラリ(jsonwebtoken、PyJWT等)は内部で正しく生のセグメント文字列を 扱っているため、この問題は基本的に発生しません。自前でJWTの検証ロジックを書いている場合に 特有の落とし穴です。
7. English summary
"invalid signature" on a JWT almost always falls into five categories: key mismatch between issuer and verifier, algorithm mismatch (HS vs RS), unresolved kid after rotation, aud/iss mismatch, and exp/nbfdrift from clock skew. Always pin an algorithm allow-list on the verifier, refresh JWKS on rotation, and never trust the token's own alg header. For inspection, use a decoder to read header and payload without verifying, then compare against your verifier config.
よくある質問 / FAQ
- invalid signature と signature verification failed の違いは?
- 両方とも署名とペイロードが一致しないことを意味します。ライブラリによって文言が違うだけで、原因は鍵不一致・alg不一致・改ざん・エンコード崩れのいずれかです。
- What causes 'invalid signature' in JWT?
- Either the signing key differs from the verification key, the algorithm differs, the token was tampered with, or the token string was corrupted (e.g. extra whitespace, URL-encoded twice).
- HS256 と RS256 は混ぜて使えるか?
- 混ぜてはいけません。alg allow-listを検証側で固定し、トークンの alg ヘッダに依存しないこと。none 攻撃・alg 混乱攻撃の原因になります。