遇到 invalid signature 時,我第一件事不是換一把 key 碰運氣,而是確認 verification 使用的仍是原始 compact JWT bytes。JWS signature 是對編碼後的 header 與 payload 計算;把 JSON decode、pretty-print 再重新 encode,即使語意相同,signature input 也可能不同。
我的排查順序固定是:
raw token
→ allowed algorithm
→ expected issuer
→ trusted key source
→ kid candidate
→ key compatibility
→ actual signature verification
→ rotation/cache
1. 保存原始 token,不重新組裝
JWS signing input 概念上是:
BASE64URL(header) + "." + BASE64URL(payload)
因此我會以 Network 或原始 request 裡的 compact token 驗證,不會拿 decode 後 JSON 重新產生一顆「看起來一樣」的 JWT。
production 環境則避免把完整 bearer token 長期寫進 log。
2. 先確認 alg 是否在 allowlist
我會在 JWT Decoder 看:
{
"alg": "RS256",
"kid": "2026-key-b"
}
但 verifier 的 policy 應該先寫死:
allowed algorithms = ["RS256"]
如果 token 自己宣告 HS256、none 或其他不允許值,我希望它在 key lookup 前就失敗。
3. 確認是不是拿錯 issuer 的 key
不同 tenant、staging/prod 或不同 identity provider 可能出現相似 kid。我會:
- 讀 token
iss; - 與 expected issuer exact match;
- 只使用該 issuer 綁定的 trusted JWKS。
拿另一個 issuer 的 public key 做 verification,本來就應該失敗。
4. kid match 只是 key selection
找到相同 kid 後,我還檢查:
- candidate 是否唯一;
kty是否相容;- JWK
alg若存在是否一致; use是否為sig;key_ops是否允許verify;- EC curve 是否正確。
JWKS Validator 會把 candidate selection 與 signature result 分開。
5. 同一個 kid 不代表同一把 key
若環境或 rotation 過程重複使用 kid,cache 裡可能還是舊 key。這時可以用 JWK Inspector 的 RFC 7638 thumbprint 比較 public key material。
thumbprint 用來比較 identity 很有用,但不會取代 issuer trust。
6. HS256 我會看 secret bytes
常見問題:
- production/staging secret 用錯;
- env value 多了 newline;
- 一邊把字串當 raw bytes,一邊先 Base64 decode;
- rotation 後 verifier 還拿舊 secret。
測試時我使用 synthetic secret,不會把 production signing secret 隨意貼到外部服務。
7. RSA/EC key format 也可能出錯
直接用 PEM 時,要分清 public key、certificate、PKCS 格式與 library 要求。ECDSA 還有 JWS signature representation 與某些 crypto API DER 格式差異。
我不自己猜格式轉換,會依 JOSE library / Web Crypto 的明確規格處理。
8. Rotation 後新 token 才失敗,就查 JWKS cache
典型情況:
old kid → pass
new kid → unknown kid / invalid signature
我會確認:
- new public JWK 是否在 signing switch 前 publish;
- CDN / verifier cache 是否 stale;
- unknown kid 是否觸發受控 refresh;
- old/new key overlap 是否足夠。
詳見 JWT Key Rotation。
9. Signature 成功仍不代表整顆 JWT 可以接受
後面仍需檢查:
iss;aud;exp;nbf;- token type;
- scope/role。
也就是:
signature valid ≠ application accepted
我會保留的 regression tests
correct key + token → pass
wrong key → fail
modified payload → fail
unexpected alg → fail
unknown kid → fail
same kid but wrong key material → fail
stale JWKS → refresh 行為明確
我不採用的「修復」
- 關閉 signature verification;
- 允許所有 algorithm;
- unknown kid 就拿任意 key 試;
- 自動信任 token 的
jku; - issuer 不驗,只在 global key pool 裡找能通過的 key;
- invalid signature 只記 warning 然後繼續。
我的 invalid signature 清單
- compact token 是否原封不動?
alg是否在 server allowlist?iss是否為 expected issuer?- key source 是否綁定 trusted issuer?
kidcandidate 是否唯一?kty/alg/use/key_ops/curve是否相容?- secret/public key bytes 是否真的正確?
- rotation/cache 是否 stale?
- crypto library 的 key/signature format 是否正確?
- signature 後是否仍做 claims validation?
我解決 invalid signature 的目標,不是讓錯誤訊息消失,而是重新建立一條能說明的信任鏈:哪個 issuer、哪個 trusted key source、哪一把 JWK、哪個 algorithm,最後真的驗證了這份原始 signing input。