Dev Study
AWSサービスの内部原理 コース

127. JWTは3つの箱がドットでつながった文字列である — 構造とJWKSによるオフライン署名検証

ユーザープールが発行したJWTを1文字ずつ分解し、なぜ「中身は誰でも読めるのに偽造だけはできない」のか、そして受け取った側がCognitoに問い合わせずにその真正性を確かめる手順を、上から下へ図で追っていきます。

① JWTは Header.Payload.Signature の3枚組である

1本のJWT文字列eyJraWQ... . eyJzdWI... . NHVaYe4...
① Header誰が・どの鍵で署名したか
② Payload中身(claims)
③ Signature封印
base64urlでデコード可
先頭eyJ → { で始まるJSON
base64urlでデコード可
先頭eyJ → { で始まるJSON
base64urlではない
  • 公式記載: 「The header and payload are base64url-encoded JSON. You can identify them by the opening characters eyJ that decode to the starting character {」
  • 形式が [JSON Header].[JSON Payload].[Signature] でなければ「it's not a valid Amazon Cognito token and you can discard it」

JWTは暗号化された塊ではなく、3区画をドットで連結しただけの構造。先頭のeyJを見ればヘッダー/ペイロードがbase64urlのJSONだと一目で分かる——つまり中身は誰でも読める。

② 3つの箱の中身 — ヘッダーは「誰の鍵で」、ペイロードは「何を主張するか」、署名は「封印」

① Headerkid: 使った鍵のID / alg: RS256
② PayloadIDトークン: user属性・iss・aud / アクセストークン: scopes・group・iss・client_id
③ Signature秘密鍵から導出したRSA256識別子・base64urlでデコード不可
どの鍵で署名したか
このトークンが主張する事実
本物である証明
  • Header: 「The key ID, kid, and the RSA algorithm, alg, that Amazon Cognito used to sign the token」
  • Payload: 「Token claims」。IDトークンはuser attributes・iss・aud、アクセストークンはscopes・group membership・iss・client_id
  • Signature: 「an RSA256 identifier derived from a signing key and parameters that you can observe at your JWKS URI」で「isn't decodable base64url like the header and payload」

ヘッダーは検証に必要なメタ情報(どの鍵か=kid、どのアルゴリズムか=alg)、ペイロードはトークンが主張する事実の束、署名だけが暗号的な封印。IDトークンは「誰であるか」、アクセストークンは「何ができるか」を運ぶ。

③ なぜ改ざんできないのか — 秘密鍵で署名、公開鍵で検証

ユーザープールごとにRSA鍵ペアを2組生成
2048-bit RSAで署名(alg=RS256)
秘密鍵(アクセストークン用)
秘密鍵(IDトークン用)
受け取った側が検証
Signatureを生成
署名の一致を照合
公開鍵は誰でも持てるJWKSで公開
一致 → 改ざんなし
不一致 → 破棄
  • 「Amazon Cognito generates two pairs of RSA cryptographic keys for each user pool. One private key signs access tokens, and the other signs ID tokens」
  • alg=RS256は「an RSA signature with SHA-256」、kidは「a truncated reference to a 2048-bit RSA private signing key」
  • 改ざんのリスク: 「A modified access token creates a risk of privilege escalation. A modified ID token creates a risk of impersonation」
  • 「if your application retrieves the public key and compares the signature, it won't match」
  • basicsLink接続: S3編・KMS編の対称暗号、SigV4のHMACは鍵を共有する検証だったが、JWTの署名は鍵を共有しない検証——署名者だけが秘密鍵を持ち、検証者は公開鍵を持つだけ。だから世界中のアプリサーバーに秘密を配らずに済む。

中身が読めることと偽造できることは別。base64urlは誰でも読み書きできるが、署名は秘密鍵を持つCognitoにしか作れず、公開鍵を持つ検証者はそれを確かめられる。改ざんは即座に露見する。

④ 公開鍵の電話帳 = JWKS — kidで引ける、認証不要のURL

JWKS URLhttps://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
誰でもGET可・認証不要
各鍵オブジェクトのフィールド
{ "keys": [ ... ] }複数の公開鍵が並ぶ配列
kid鍵の識別子(検索キー)
algRS256
ktyRSA
nモジュラス
e指数(例: AQAB)
usesig(署名検証用)
  • サンプルjwks.jsonはkeys配列に複数の鍵。各鍵はkid・alg=RS256・kty=RSA・e・n・use=sigを持つ
  • nは「the modulus value for the RSA public key」、eは「the exponent value」(ともにBase64urlUInt-encoded)、useのsigは「represents signature」
  • 「The JWKS URI contains public information about the private key that signed your user's token」——公開されるのは公開鍵の情報だけ
  • basicsLink接続: JWKSは誰でも引ける電話帳で、kidはそのインデックス。トークンのkidを検索キーに公開鍵を引き当てる発想は、DynamoDB編のパーティションキーによるキー検索と同じ原理。

公開鍵は秘密ではないので、認証なしのURLで堂々と公開できる。検証者はこの電話帳をkidで引くだけでよい。

⑤ 検証手順 — デコード→kid照合→署名検証→claims検証

受け取ったJWT
(2) kidを照合
ヘッダー/ペイロードを復元base64urlから
(3) 署名を検証
トークンのkid
キャッシュ済みJWKSのkid
(4) claimsを検証
公開鍵でSignatureを検算不一致ならここで破棄
すべて満たすと
exp期限切れでないか
aud/client_idapp client IDと一致
iss発行者URLと一致
token_useid/accessの種類が一致
中身を信頼できる
  • (2)「Compare the local key ID (kid) to the public kid」「Search the public JSON Web Key for a kid that matches the kid of your JWT」。見慣れないkidが来た場合はJWKS再取得(§⑥)
  • (4) expは現在時刻と比較。「The aud claim in an ID token and the client_id claim in an access token must match the app client ID」
  • 「The issuer (iss) claim must match your user pool」(例 https://cognito-idp.us-east-1.amazonaws.com/{userpoolID})
  • token_useはaccessのみなら access、IDのみなら id

署名検証は「偽造されていないか」、claims検証は「そもそも誰宛て・いつまで・どの種類のトークンか」を確かめる別々の関門。両方通過して初めて信頼できる。

⑥ この設計の要点 — Cognitoに問い合わせないオフライン検証

素朴な発想トークンが来るたびCognitoに問い合わせ
以後のトークンは
JWKSを一度取得しキャッシュkidをキャッシュキーに
鍵ローテーションに備え
手元の公開鍵だけで署名検証Cognitoへの往復ゼロ
JWKSを再取得
見慣れないkidが来た
キャッシュ更新また手元で完結
  • 「cache public keys in your app, using the kid as a cache key, and refresh the cache periodically」
  • 「If you receive a token with the correct issuer but a different kid, Amazon Cognito might have rotated the signing key. Refresh the cache from your user pool jwks_uri endpoint」
  • basicsLink接続: 「署名者に問い合わせず、手元の情報だけで検証を完結させる」はキャッシュ編・KMS封筒暗号(往復するのは小さなデータ鍵だけ)と同じ経済性の追求。JWKSという小さな公開鍵の束を一度取れば、以後何百万件の検証もネットワークを使わない。

JWKSキャッシュ+kid照合という設計により、検証は「取ってきた公開鍵で手元で計算するだけ」に還元される。認証がCognitoの処理能力に律速されず、スケールする。鍵ローテーションだけはkidの変化を合図に再取得すればよい。

公式ドキュメントで詳しく ↗