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, e | RSA 公開鍵のパラメータ | Base64url エンコードされた値 |
運用で効いてくるのは kid である。JWT を受け取ったサーバーは、JWT ヘッダーの kid と JWKS 内の kid を照合し、どの鍵で署名検証すべきかを特定する。ただし RFC 7517 上の kid は省略可能なメンバーで、必須なのは kty だけだという点は押さえておきたい。鍵が 1 本の JWKS なら kid 無しでも成立するが、鍵が複数並ぶ運用では受け手が鍵を順に試すしかなくなる。仕様も、1 つの JWKS 内では鍵ごとに異なる kid を使うべきだとしている (例外として挙げられているのは、鍵の種類だけが違う等価な代替鍵を同じ kid で並べる場合)。use と alg も省略可能なので、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 署名検証の流れ
- クライアントが JWT をサーバーに送信する
- サーバーは JWT ヘッダーから
kidを取得する - JWKS エンドポイントから公開鍵セットを取得する (キャッシュがあればキャッシュから)
kidが一致する JWK を見つける- その公開鍵で JWT の署名を検証する
- 署名が有効なら、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 から取り直すことである。
発行側から見たローテーションは次の順に進む。
- 新しい鍵ペア (key-2) を生成し、JWKS に追加する
- 新しい JWT は key-2 で署名する
- 古い鍵 (key-1) で署名された JWT がまだ有効期限内なので、key-1 も JWKS に残す
- key-1 で署名された JWT が全て期限切れになったら、key-1 を JWKS から削除する
クライアント側の実装で重要なのは、JWKS をキャッシュしつつ、未知の kid に遭遇したら JWKS を再取得するロジックだ。キャッシュだけに頼ると、ローテーション直後に検証が失敗する。
逆に、未知の kid を見たら無条件に取り直す実装をそのまま置くと、でたらめな kid を書いたトークンを送り続けるだけで JWKS エンドポイントへのリクエストを増幅させられる。jwks-rsa には rateLimit と jwksRequestsPerMinute (既定は毎分 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 bytes | 64 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) を基に、アプリケーション側で別途判断する必要がある。
体系的に学ぶなら関連書籍を参照してほしい。
この記事は役に立ちましたか?
関連用語
JWT
JSON Web Token の略で、署名付きの JSON でユーザー情報を安全に伝達するトークン形式
OAuth 2.0
ユーザーのパスワードを渡さずに、リソースへの限定的なアクセスを許可する認可フレームワーク
Amazon Cognito
Web / モバイルアプリに認証 / 認可機能を追加する AWS マネージドサービス
ゼロトラスト
ネットワークの内外を問わず全てのアクセスを検証し、暗黙の信頼を排除するセキュリティモデル
OpenID Connect
OAuth 2.0 の上に構築された認証レイヤーで、ユーザーの身元情報を ID トークンとして提供する
リフレッシュトークン
アクセストークンの有効期限切れ後に、再認証なしで新しいアクセストークンを取得するためのトークン