JWK

暗号鍵を JSON 形式で表現する標準仕様 (RFC 7517)。JWKS エンドポイントによる公開鍵の配布、kid で鍵を特定する仕組みと必須メンバー、鍵ローテーション時のキャッシュ戦略、RSA と EC の選択を解説

認証セキュリティ

JWK とは

JWK (JSON Web Key) は、暗号鍵を JSON 形式で表現する標準仕様 (RFC 7517) である。JWT の署名検証に使う公開鍵を、HTTP エンドポイント経由で安全に配布するために広く使われている。

従来、公開鍵の配布は PEM ファイルの手動コピーや X.509 証明書の配布に依存していた。JWK はこれを JSON + HTTPS という Web ネイティブな方式に置き換え、鍵のローテーションや複数鍵の管理を自動化可能にした。OAuth 2.0 / OpenID Connect のエコシステムで事実上の標準となっている。

JWK の構造

JWK は鍵 1 本を表す JSON オブジェクトで、鍵の材料とその扱い方をメンバー名で並べる。必須は鍵の種類を示す kty だけで、残りはすべて省略可能である。

{
  "kty": "RSA",
  "kid": "2026-03-key-1",
  "use": "sig",
  "alg": "RS256",
  "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2...",
  "e": "AQAB"
}
フィールド意味
kty鍵の種類 (Key Type)RSA, EC, OKP
kid鍵の識別子 (Key ID)任意の文字列。JWT ヘッダーの kid と照合
use用途sig (署名), enc (暗号化)
algアルゴリズムRS256, ES256
n, eRSA 公開鍵のパラメータBase64url エンコードされた値

運用で効いてくるのは kid である。JWT を受け取ったサーバーは、JWT ヘッダーの kid と JWKS 内の kid を照合し、どの鍵で署名検証すべきかを特定する。ただし RFC 7517 上の kid は省略可能なメンバーで、必須なのは kty だけだという点は押さえておきたい。鍵が 1 本の JWKS なら kid 無しでも成立するが、鍵が複数並ぶ運用では受け手が鍵を順に試すしかなくなる。仕様も、1 つの JWKS 内では鍵ごとに異なる kid を使うべきだとしている (例外として挙げられているのは、鍵の種類だけが違う等価な代替鍵を同じ kid で並べる場合)。usealg も省略可能なので、alg が入っていない JWK を受け取る前提で、検証側が許可するアルゴリズムを自分で固定しておく。

JWKS エンドポイント

認証プロバイダーは、公開鍵のセットを JWKS (JSON Web Key Set) エンドポイントで公開する。

# Cognito
https://cognito-idp.ap-northeast-1.amazonaws.com/{userPoolId}/.well-known/jwks.json

# Auth0
https://{tenant}.auth0.com/.well-known/jwks.json

# Google
https://www.googleapis.com/oauth2/v3/certs

レスポンスは keys 配列に複数の JWK を含む。

{
  "keys": [
    { "kid": "key-1", "kty": "RSA", "use": "sig", ... },
    { "kid": "key-2", "kty": "RSA", "use": "sig", ... }
  ]
}

複数の鍵が並ぶ主な理由はローテーションである。RFC 7517 が kid の用途として挙げているのもこの場面で、切り替えの最中は古い鍵で署名されたまだ期限内のトークンと新しい鍵のトークンが同時に流れるため、どちらの鍵も配布しておく必要がある。ただし理由はそれだけではない。Cognito はユーザープールごとに RSA の鍵ペアを 2 組生成し、一方をアクセストークン、他方を ID トークンの署名に使う。用途 (use) やアルゴリズム (alg) の違いで並ぶこともあるので、鍵の絞り込みは kid の一致だけに任せず、想定するアルゴリズムかどうかも確認したい。

JWT 署名検証の流れ

  1. クライアントが JWT をサーバーに送信する
  2. サーバーは JWT ヘッダーから kid を取得する
  3. JWKS エンドポイントから公開鍵セットを取得する (キャッシュがあればキャッシュから)
  4. kid が一致する JWK を見つける
  5. その公開鍵で JWT の署名を検証する
  6. 署名が有効なら、JWT のペイロード (claims) を信頼する
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

const client = jwksClient({
  jwksUri: `https://cognito-idp.ap-northeast-1.amazonaws.com/${userPoolId}/.well-known/jwks.json`,
  cache: true,           // JWKS をキャッシュ
  cacheMaxAge: 600000,   // 10 分間キャッシュ (jwks-rsa の既定値)
});

function getKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) {
  client.getSigningKey(header.kid!, (err, key) => {
    callback(err, key?.getPublicKey());
  });
}

jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
  if (err) throw new Error('JWT 検証失敗');
  // decoded にペイロードが入る
});

鍵のローテーション

認証プロバイダーは署名鍵をローテーションすることがある。ここで実装に埋め込みたくなるのが「何日ごとに変わる」という周期の前提だが、Cognito の公式ドキュメントの書き方は「ユーザープールの署名鍵をローテーションする可能性がある」であり、周期も時刻も公開されていない (2026 年 8 月時点)。案内されている対処は 2 つで、公開鍵を kid をキーにしてキャッシュし定期的に更新すること、そして発行者は正しいのに見覚えのない kid のトークンが来たら鍵がローテーションされたものとして jwks_uri から取り直すことである。

発行側から見たローテーションは次の順に進む。

  1. 新しい鍵ペア (key-2) を生成し、JWKS に追加する
  2. 新しい JWT は key-2 で署名する
  3. 古い鍵 (key-1) で署名された JWT がまだ有効期限内なので、key-1 も JWKS に残す
  4. key-1 で署名された JWT が全て期限切れになったら、key-1 を JWKS から削除する

クライアント側の実装で重要なのは、JWKS をキャッシュしつつ、未知の kid に遭遇したら JWKS を再取得するロジックだ。キャッシュだけに頼ると、ローテーション直後に検証が失敗する。

逆に、未知の kid を見たら無条件に取り直す実装をそのまま置くと、でたらめな kid を書いたトークンを送り続けるだけで JWKS エンドポイントへのリクエストを増幅させられる。jwks-rsa には rateLimitjwksRequestsPerMinute (既定は毎分 10 回) があり、再取得の頻度に上限を掛けられる。キャッシュ期間 cacheMaxAge の既定値 10 分は「ローテーション直後の最大 10 分間は古い鍵セットを見る」という意味でもあるので、検証失敗をそのまま 401 で返すか、1 度だけ再取得してから判定するかを決めておく。

API Gateway での JWT 検証

API Gateway (HTTP API) の JWT オーソライザーを使えば、Lambda 側で検証ロジックを実装する必要がない。

API Gateway が自動的に行う処理:

  • JWKS エンドポイントから公開鍵を取得・キャッシュ (最大 2 時間)
  • JWT の署名検証 (対応するのは RSA 系のアルゴリズムのみ)
  • exp (有効期限)・nbf (有効期間の開始)・iat (発行時刻) のチェック
  • iss (発行者) と aud (対象者) の検証
  • ルートにスコープを設定した場合は scope または scp の照合

詰まりやすいのは aud の扱いだ。API Gateway は aud があればそれを照合し、aud が無い場合に限って client_id を照合する。aud を持たず client_id を持つ Cognito のアクセストークンでも、オーソライザーの audience にアプリクライアント ID を設定しておけば通る (2026 年 8 月時点)。公開鍵のキャッシュが最大 2 時間ある点も設計に効く。鍵をローテーションするなら、新旧どちらの鍵でも検証できる猶予期間を挟まないと、キャッシュが切れるまで検証が失敗し続ける。

Lambda には検証済みの claims がイベントオブジェクトに含まれて渡される。これにより、Lambda は認証ロジックを一切持たず、ビジネスロジックに集中できる。検証されるのは署名と上記の claims までなので、サインアウト済みのトークンを弾きたいなら別の仕組みが必要になる。

RSA と EC の選択

JWK で使われる鍵の種類は主に RSA と EC (楕円曲線) の 2 つだ。

観点RSA (RS256)EC (ES256)
鍵サイズ2048 bit (256 bytes)256 bit (32 bytes)
署名サイズ256 bytes64 bytes
検証速度速いやや遅い
署名速度遅い速い
互換性ほぼ全てのライブラリが対応一部の古いライブラリで非対応

Cognito の署名鍵は 2048 bit の RSA で、alg は RS256 に固定されている。API Gateway (HTTP API) の JWT オーソライザーも RSA 系のアルゴリズムだけを受け付ける (2026 年 8 月時点)。つまり AWS のマネージドな経路に乗せる前提では EC を選ぶ余地がない。EC が候補に入るのは、Auth0 のように鍵の種類を選べるプロバイダーを使う場合や、自前で発行して自前で検証する場合だ。判断材料は署名サイズで、トークンをヘッダーに載せて何度も往復させる API や帯域が限られるモバイル環境では 64 bytes と 256 bytes の差が効いてくる。

よくある誤解

「JWKS エンドポイントは秘密にすべき」

JWKS エンドポイントは公開鍵を配布するためのものであり、秘密にする必要はない。公開鍵は文字通り「公開」するための鍵だ。秘密にすべきなのは署名に使う秘密鍵であり、これは認証プロバイダーの内部に保持される。

「JWT を検証すれば認可も完了」

JWT の署名検証は認証 (このトークンは本物か) を確認するだけだ。認可 (このユーザーにこの操作を許可するか) は、JWT のペイロードに含まれる claims (roles, scopes) を基に、アプリケーション側で別途判断する必要がある。

体系的に学ぶなら関連書籍を参照してほしい。

この記事は役に立ちましたか?

関連用語

関連する記事