JWTの仕組みを完全理解|Cognito・STS・公開鍵検証まで図解で解説

AWS

WebアプリやモバイルアプリにおけるJWT(JSON Web Token)は、現代の認証基盤を支えるコア技術です。

この記事では、JWTの3パート構造から始まり、OpenID Connect・Amazon Cognito・AWS STSとの関係、公開鍵暗号方式による署名検証の仕組み、そしてサーバーが公開鍵を取得するタイミングまでを体系的に解説します。「なんとなく使っているJWTをちゃんと理解したい」エンジニアの方に最適な内容です。


なぜJWTが必要なのか?

WebアプリやモバイルアプリではユーザーがOKかを確かめる認証が欠かせません。しかしユーザー数が増えるにつれて「毎回データベースを検索して確認する」方式では処理が重くなっていきます。

JWTを使えば、サーバーはデータベースに問い合わせることなく、トークン自体を検証するだけでユーザーを識別できます。これがJWTが広く普及している最大の理由です。


JWTの構造

JWT(JSON Web Token)は3つのパートがドット(.)で区切られた文字列です。RFC 7519として国際標準化されています。

パート内容主なフィールド
ヘッダー署名アルゴリズム・鍵IDなどのメタ情報alg, kid
ペイロードユーザー情報・有効期限などのクレームsub, exp, iss, aud
署名改ざんされていないことを証明する秘密鍵で生成されたハッシュ値

⚠️ 重要:各パートはBase64URLエンコードされているだけで暗号化されていません。ペイロードの中身は誰でもデコードして読めます。パスワードなどの機密情報は絶対に含めないでください。


OpenID ConnectとOAuth 2.0

JWTの発行に深く関わる2つのプロトコルを整理します。

プロトコル役割扱うもの
OAuth 2.0「このアプリに〇〇へのアクセスを許可する」という認可の仕組み何ができるか
OpenID Connect(OIDC)OAuth 2.0の上に「誰であるか(認証)」の仕組みを追加したプロトコル誰が

つまり OpenID Connect = OAuth 2.0 + 認証 です。Amazon CognitoはこのOIDCに準拠しており、認証後にJWT形式のIDトークンを発行します。


Amazon CognitoとJWT発行の仕組み

Amazon Cognitoはユーザー認証を管理するAWSのサービスです。ログインが成功するとOIDCの仕様に従い、以下の3種類のトークンを発行します。

  1. IDトークン(JWT):「このユーザーは誰か」という身元情報。ユーザーID・メールアドレス・有効期限などが含まれる。
  2. アクセストークン:APIを呼び出すための権限証明。
  3. リフレッシュトークン:IDトークン・アクセストークンの有効期限が切れた際に再発行するために使う。

CognitoがJWTの署名を作る仕組みは以下のとおりです。

// JWT ヘッダー(デコード後)
{
  "alg": "RS256",   // 署名アルゴリズム(RSA + SHA-256)
  "kid": "abc123"   // この署名に使った秘密鍵のID
}

// 署名の生成イメージ
署名 = RSA秘密鍵で暗号化(
  SHA256( Base64URL(ヘッダー) + "." + Base64URL(ペイロード) )
)


公開鍵暗号方式による署名検証

JWTの署名検証には公開鍵暗号方式が使われます。秘密鍵と公開鍵の役割を理解することが署名検証の核心です。

鍵の種類所有者用途公開可否
秘密鍵(Private Key)Cognitoのみ署名の「作成」絶対に非公開
公開鍵(Public Key)誰でも取得可能署名の「検証」JWKSエンドポイントで公開

秘密鍵で作成した署名は、対応する公開鍵でしか検証できません。これにより「この署名はCognitoが作ったもの」と誰でも確認できます。

CognitoはOIDCの標準仕様に従い、公開鍵の一覧を以下のURLで公開しています。

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
// レスポンス例(公開鍵の一覧)
{
  "keys": [
    {
      "kid": "abc123",      // JWTヘッダーのkidと照合する
      "kty": "RSA",
      "alg": "RS256",
      "n":   "uBp47VGURf...", // 公開鍵の値
      "e":   "AQAB"
    }
  ]
}

💡 印鑑証明に例えると…Cognitoの秘密鍵=本人だけが持つ「印鑑」、JWTの署名=その印鑑を押した「印影」、JWKSエンドポイント=「印鑑証明が登録された役所」。検証する側は役所で公開されている印鑑証明(公開鍵)と実際の印影(署名)を照合するだけです。毎回Cognitoに問い合わせる必要はありません。


署名検証の仕組みを深掘りする

ヘッダーとペイロードはBase64URLエンコードされているだけで、中身は誰でも読めます(暗号化ではありません)。署名だけが秘密鍵で暗号化されています。署名検証は以下の2経路で行われます。

受け取ったJWT
┌───────────────────────────────────────┐
│ヘッダー  │  ペイロード  │  署名         │
└───────────────────────────────────────┘
      │              │         │
      └──────┬────────┘         │
             ↓                  ↓
  SHA-256でハッシュ化      公開鍵で復号(kidで特定した公開鍵を使用)
             ↓                  ↓
       ハッシュ値B         ハッシュ値A
             │                  │
             └────── 比較 ───────┘
                      ↓
           A === B → ✅ 検証成功(本物&改ざんなし)
           A ≠ B   → ❌ 改ざんあり or 偽造トークン

この検証で証明できることは以下の2点です。

  • Cognitoが発行した本物のJWT:秘密鍵を持つCognitoだけが正しい署名を作れるため
  • ヘッダー・ペイロードが改ざんされていない:改ざんするとハッシュ値Bが変わりAと一致しなくなるため

💡 JWSとJWEの違い:JWTの目的は「データを隠すこと」ではなく「データが本物であることを証明すること」です。内容を隠したい場合はJWE(JWT Encryption)で全体を暗号化します。本物の証明のみが目的であれば、今回解説したJWS(署名)で十分です。通常のIDトークンはJWSで十分とされており、ペイロードに機密情報を含めないことがルールです。

なお、JWTには「一度発行したトークンは有効期限が来るまで無効化できない」という弱点があります。主な対策は以下のとおりです。

  • 有効期限を短くする(15分〜1時間程度)← 推奨
  • リフレッシュトークンで再発行する仕組みを用意する ← 推奨
  • ブラックリスト管理(DBに登録して弾く)← DBアクセスが復活するためトレードオフあり


AWS STSと一時クレデンシャル

AWS STS(Security Token Service)は、AWSリソースへのアクセスに必要な一時的な認証情報(クレデンシャル)を発行するサービスです。

❌ やってはいけないこと:AWSの長期アクセスキーをアプリに直接埋め込むこと。アプリが解析されると認証情報が永続的に漏洩します。
✅ 正しいアプローチ:CognitoでJWTを取得 → STSに渡して有効期限付きの一時クレデンシャルを発行してもらう。

CognitoのJWTからSTSクレデンシャルを得るまでの流れは以下のとおりです。

  1. ユーザーがCognitoでログイン → CognitoがJWT(IDトークン)を発行してアプリに返す
  2. アプリがSTSのAPI(AssumeRoleWithWebIdentity)を呼び出す
  3. STSがCognitoのJWKSエンドポイントから公開鍵を取得してJWTを検証する(Cognitoへの直接問い合わせは不要)
  4. STSが一時クレデンシャルを返す
  5. アプリが一時クレデンシャルでAWSリソースにアクセス(有効期限が切れたら②から再実行)
aws sts assume-role-with-web-identity 
  --role-arn arn:aws:iam::123456789:role/MyAppRole 
  --web-identity-token <CognitoのIDトークン> 
  --role-session-name MySession
// STSから返される一時クレデンシャル
{
  "AccessKeyId":     "ASIAIOSFODNN7EXAMPLE",
  "SecretAccessKey": "wJalrXUtnFEMI/...",
  "SessionToken":    "AQoDYXdzE...",
  "Expiration":      "2026-04-17T13:00:00Z"  // 有効期限(例:1時間)
}

💡 フェスティバルの入場に例えると…Cognito=チケット販売所(JWT発行)、STS=会場の入口スタッフ(チケット確認&リストバンド発行)、リストバンド=有効期限付きの一時クレデンシャル。期限が来たら再発行が必要で、万一盗まれても一定時間後に無効になります。


WebアプリでのJWT送受信と検証

通常のWebアプリでも同じ公開鍵検証の仕組みが使われます。クライアントは一般的にHTTPの Authorizationヘッダーにトークンを含めて送信します。

GET /api/profile HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
               ↑ "Bearer"=「このトークンを持参した人に権限を与えてください」

サーバー側でのJWT検証は以下のステップで行います。

  1. AuthorizationヘッダーからJWTを取り出す
  2. JWTをヘッダー・ペイロード・署名に分割する
  3. ヘッダーのkidで対応する公開鍵を特定する
  4. 公開鍵で署名を検証する(改ざん検出)
  5. ペイロードのクレームを検証する
  6. すべてOKならリクエストを処理する

ステップ5で確認すべきクレームは以下のとおりです。

クレーム意味確認しないと…
exp有効期限期限切れトークンが使い回される
iss発行者別サービスのJWTで偽装できてしまう
aud対象者他サービス向けJWTが使われてしまう
subユーザーIDどのユーザーか特定できない


サーバーはいつ公開鍵を取得するのか

JWTが届くたびにJWKSエンドポイントへアクセスしていたらネットワーク通信が大量に発生します。実際には3つのパターンがあります。

パターン取得タイミング注意点
① サーバー起動時に取得起動時にJWKSから公開鍵を取得してメモリにキャッシュ鍵ローテーション時に古い鍵を使い続けるリスクがある
② 初回リクエスト時に取得(Lazy Loading)最初のJWT検証が来たときに初めて取得してキャッシュ起動を軽くしたい場合に有効。ローテーション対応は①と同様
③ kidが見つからないとき再取得(推奨)キャッシュにkidがなければJWKSを再取得してキャッシュ更新鍵ローテーションに自動追従できる。多くのライブラリが採用

鍵ローテーションとは、Cognitoが使用する秘密鍵を定期的に切り替えることです。kidが変わるため、キャッシュに存在しない場合は再取得が必要になります。

// 鍵の更新前
Cognito: kid="abc" の秘密鍵でJWTに署名 → アプリに発行

// 鍵の更新後(ローテーション)
Cognito: kid="xyz" の秘密鍵でJWTに署名 → アプリに発行
         ↑ kidが変わる!キャッシュに"xyz"がなければ再取得が必要

JWKSエンドポイントのレスポンスには通常 Cache-Control ヘッダーが含まれます。

Cache-Control: public, max-age=3600
                            ↑ 1時間キャッシュを推奨(例)

💡 実際の開発では、Node.jsなら jsonwebtoken + jwks-rsa、Javaなら java-jwt などのJWT検証ライブラリがキャッシュ管理・再取得ロジックを自動で処理してくれます。実務では自前実装することはほぼありません。


まとめ

用語・概念役割・意味
JWTヘッダー・ペイロード・署名の3部構成トークン
OpenID ConnectOAuth 2.0に認証を追加した標準プロトコル
Amazon CognitoOIDCに準拠したユーザー認証サービス
秘密鍵 / 公開鍵秘密鍵で署名「作成」、公開鍵で署名「検証」
kid対応する公開鍵を特定するための鍵ID
AWS STSJWTを検証して一時クレデンシャルを発行
署名検証の仕組み①署名を公開鍵で復号→ハッシュ値A、②ヘッダー+ペイロードをSHA-256→ハッシュ値B、A=Bなら検証成功
JWSとJWEJWS=署名で本物を証明(平文)、JWE=全体を暗号化して内容を隠す
Bearer認証Authorization: Bearer <JWT> ヘッダーで送る
公開鍵の取得タイミング「kidが見つからないときだけ再取得」が最も堅牢。鍵ローテーションに自動追従できる

✨ この記事の最重要ポイント
JWT検証の核心は「発行者に毎回問い合わせることなく、公開鍵だけでローカルに自己完結して検証できる」点にあります。検証の具体的な仕組みは「①署名を公開鍵で復号してハッシュ値Aを得る・②ヘッダー+ペイロードをSHA-256でハッシュしてBを得る・A=Bなら本物と証明」という2経路の比較です。そして公開鍵のキャッシュ管理では、kidが見つからないときだけ再取得することで鍵ローテーションにも自動追従できます。


参考リソース

コメント

タイトルとURLをコピーしました