kid not found、不明な key ID、key rotation 後の署名エラーを見たとき、私は payload より先に protected header を見ます。kid は信頼済み JWK Set から候補 key を選ぶための identifier だからです。
ただし目標は「何でもいいから verify させる」ことではありません。正しい issuer が管理する key set から、application が許可した algorithm に適合する key を選んだと説明できることが重要です。
JWT と JWKS があるなら JWKS Validator で alg / kid / iss と正確な key match を確認します。
Step 1: header の kid を読む
{
"alg": "RS256",
"typ": "JWT",
"kid": "2026-09-key-03"
}
RFC 7515 / RFC 7517 の kid は Key ID です。JWK Set の中から特定の key を選ぶとき、特に rollover で役立ちます。
しかし kid は key selection の手掛かりです。信頼済み key であることの証明ではありません。未検証 token は好きな kid を書けます。
Step 2: 任意の key source ではなく、信頼する JWKS を見る
{
"keys": [
{ "kty": "RSA", "kid": "2026-09-key-03", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" },
{ "kty": "RSA", "kid": "2026-06-key-02", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" }
]
}
私は application が信頼する issuer の JWKS 内で exact kid match を探します。
web 全体で同じ key ID を検索しません。kid は global に一意ではなく、同じ label の public key を見つけても trust は確立しません。
OAuth / OIDC の metadata を使うなら信頼経路は:
configured issuer
↓
trusted metadata
↓
trusted jwks_uri
↓
JWK Set
↓
kid match
です。
Step 3: kid がない場合
すべての signed JWT に kid が必須ではありません。
信頼する JWKS に compatible signing key が 1 個だけなら、verifier policy で一意に選べる場合があります。
しかし:
{
"keys": [
{ "kty": "RSA", "kid": "key-a", "alg": "RS256" },
{ "kty": "RSA", "kid": "key-b", "alg": "RS256" }
]
}
で token header が:
{ "alg": "RS256" }
だけなら、明確な rule なしに「全部試して通ったものを使う」という実装を避けます。
JWKS Validator も複数の compatible key が残れば ambiguous と扱います。
Step 4: alg / kty / use / key_ops
kid match 後も次を確認します。
- JWT
alg - JWK
kty - JWK
algがあれば一致 - JWK
useがあればsig - JWK
key_opsがあればverify
RS256 なら RSA verification key、ES256 なら P-256 EC key など、algorithm family と key type の compatibility が必要です。
application は algorithm allowlist を持ち、token の alg を policy として無条件に採用しません。
Step 5: kid not found なら rotation を確認する
典型的な流れ:
issuer publishes key A
↓
A で token を sign
↓
issuer introduces key B
↓
new token uses kid B
↓
verifier still caches only A
すると kid B not found になります。
私は:
- issuer が current JWKS に B を publish しているか。
- verifier の cache TTL。
- missing
kidで controlled refresh するか。 - dev/old tenant の JWKS を見ていないか。
を確認します。
毎 request で network fetch するのではなく、controlled cache / refresh strategy を使います。
Step 6: environment / tenant の取り違え
JWT iss: https://login.dev.example.com/
API trusts: https://login.example.com/
なら key set が違うのは自然です。
kid not found を見たとき「この key はどこ?」だけでなく「この token はそもそも想定 issuer / environment から来たか?」と確認します。
issuer validation ガイド と一緒に見る理由です。
Step 7: jku / x5u を自動で信頼しない
JOSE header の URL は token-controlled input です。
RFC 8725 は arbitrary URL follow による SSRF / trust failure を警告しています。
malicious token
→ attacker URL を key source として指定
→ server が fetch
→ attacker's key を trust
という流れにはしません。
JWKS Validator も jku / x5u を自動 follow せず、ユーザーが明示した JWKS URL だけを fetch します。
Step 8: kid matched と signature verified を分ける
kid found ✓
algorithm compatible ✓
key intended for sig ✓
signature verified ✓ / ✕
最初の 3 つが通っても cryptographic verification は別です。failure なら invalid signature ガイド に進みます。
Step 9: signature 後も claims を validate
signature が通った後に:
issaudexpnbf- token type/profile
- scopes / roles
を確認します。
cryptographically valid な token でも current resource server 向けとは限りません。
私の kid debugging checklist
- JWT を decode して
alg/kid/issを読む。 - trusted issuer を決める。
- その issuer の trusted JWKS を得る。
- exact
kidmatch を探す。 kty/alg/use/key_opsを確認する。- missing key なら rotation / cache を調べる。
- dev/prod / tenant の取り違えを調べる。
- token が指定する任意の鍵 URL を trust しない。
- selected JWK で signature を verify する。
- issuer / audience / time claims を続けて validate する。
私が最後まで守る考え方は、kid は key を見つけるための手掛かりであって、その key を trust すべき理由ではないということです。