+ 作る
図を読み込んでいます…
1 / 13
図でつながる 認証と暗号の基礎 / 全11章

第8章 JWT ― 中身の読める札に、改ざん防止の印を付ける

発行
かえで
版
第2版
改訂
分量
13コマ・5,929字

JWT は、中身を書いた札に改ざん防止の印を付けて渡す形式です。三つの部分、誰でも読める Base64url、クレーム、署名と鍵、検証の関門、宛先と失効の落とし穴を、図の動きで順にたどります。

セッション ID は、サーバーの記録を指す番号

第1章では、ログインに成功したアオイにサーバーが セッション ID を渡す流れを見ました。セッション ID は意味を持たない長い番号で、それ自体には「誰か」も「いつまで」も書かれていません。サーバーは手元に「7f3a… はアオイのログイン」という記録を持っていて、アオイが番号を見せるたびに、その記録を引いて照らし合わせます。

図では、アオイが番号の札をサーバーに見せ、サーバーが右の記録と照らし合わせています。札が盗まれても、サーバー側で記録を消せばその番号は使えなくなるので、止めるのは簡単です。そのかわり、札を受け取る側は、いつでも記録を引ける場所にいなければなりません。サーバーが何台にも分かれていたり、別の組織の API が札を受け取ったりすると、この照らし合わせが手間になります。

この章では、もうひとつのやり方を見ていきます。番号ではなく、中身そのもの を札に書いて渡す方法です。

  • セッション ID は、サーバーの記録を指す番号
  • セッションID 7f3a…
  • 人
  • サーバー
  • データベース
  • 毎回見せる
  • 照合
  • アオイ
  • アプリのサーバー
  • ログイン中の記録
  • 7f3a… → アオイ
  • 番号そのものには、誰か・いつまでは書かれていない

中身を書いた札に、改ざん防止の印を付ける

もうひとつのやり方は、「この札はアオイのもので、今日の 17 時まで有効」という中身を札に直接書き、発行元が 改ざん防止の印 を付けて渡すことです。受け取った側は記録を引かずに、印が本物か、書き換えられていないかを確かめるだけで、中身を信用できます。この形式が JWT(JSON Web Token) です。仕様では「ジョット」と読むよう勧めています。図では、番号だけの札が中身の書かれた札に変わり、紫の印が付きました。右の記録は、もう引いていません。

たとえるなら、発行元の印が押された入館証です。受付は名簿をめくらなくても、印を見れば通してよいと分かります。ただし、このたとえには限界があります。入館証なら顔写真を警備員が見比べますが、JWT は札を 持っている人なら誰でも 使えます。また、配ったあとで回収しにくい点は入館証と同じで、これはあとのページで扱います。

JWT が決めているのは、札の 書き方(データの形式) です。どうやってログインさせるか、誰に何を許すかは、JWT の外で決めます。仕様は RFC 7519 にまとまっています。

  • 中身を書いた札に、改ざん防止の印を付ける
  • ヘッダー alg: RS256 typ: JWT
  • ペイロード sub: aoi exp: 1794038400
  • 署名 前の二つへの印
  • sub: アオイ exp: 17:00
  • 人
  • 印
  • サーバー
  • OK
  • データベース
  • 見せる
  • アオイ
  • アプリのサーバー
  • ログイン中の記録
  • 印を確かめる
  • 発行元の印が押された入館証のようなもの

ドットで区切られた三つの部分

JWT を実際に見ると、英数字が長く続く1本の文字列です。よく見ると2つのドット(.)で3つに区切られていて、前から ヘッダー、ペイロード、署名 と呼びます。図では、上の1本の文字列が、下の3枚のカードに分かれました。

ヘッダーには「どの方式で印を付けたか」を表す alg などの付帯情報が入ります。ペイロードは札の本題で、「誰について」「いつまで」といった中身が入ります。三つ目の署名は、前の二つに対して付けた改ざん防止の印です。前のページの紫の印は、ここに当たります。

ここで扱うのは、JWS(JSON Web Signature、RFC 7515)の Compact 形式 と呼ばれる、いちばんよく見かける形です。ほかに、中身そのものを暗号化した JWE という形もあり、こちらは5つに区切られていて、鍵を持たない人には中身が読めません。

  • ドットで区切られた三つの部分
  • eyJhbGciOiJSUzI1NiIs…
  • .
  • eyJzdWIiOiJhb2kiLCJleHAi…
  • .
  • SflKxwRJSMeKKF2Q…
  • 印
  • ヘッダー alg: RS256 typ: JWT
  • ペイロード sub: aoi exp: 1794038400
  • 署名 前の二つへの印
  • ここで扱うのは JWS Compact 形式(JWE は5つに区切られる)

Base64url は、誰でも読み戻せる

ヘッダーとペイロードの文字列は、JSON を Base64url という方法で書き直したものです。Base64url は、どんなデータも英数字と「-」「_」だけで表せるように置き換える決まりで、URL の中にもそのまま入れられます。置き換えの表は公開されていて鍵も使わないので、誰でも元の JSON に戻せます。暗号化とは違い、隠す働きはありません。

図では、真ん中の文字列をデコード(元に戻すこと)しただけで、アオイの名前や有効期限が丸見えになり、右の盗み見る人にも読めています。JWT は通信の途中やブラウザの保存場所、サーバーのログなどに残りやすいので、ペイロードには 見られて困るものを入れない のが基本です。パスワードや秘密鍵はもちろん、必要以上の個人情報も避けます。

裏を返すと、「読めた」ことは何の保証にもなりません。誰でも好きな JSON を書いて Base64url にできるからです。中身を信じてよいかどうかは、このあと見る署名の検証で決まります。

  • Base64url は、誰でも読み戻せる
  • eyJhbGciOiJSUzI1NiIs…
  • .
  • eyJzdWIiOiJhb2kiLCJleHAi…
  • .
  • SflKxwRJSMeKKF2Q…
  • デコード(鍵は不要)
  • { "sub": "aoi", "name": "アオイ", "exp": 1794038400 }
  • 人
  • 注意
  • パスワードや 秘密は入れない
  • 見る
  • 盗み見る人
  • Base64url は文字の置き換え。暗号化ではない

誰が、誰について、誰に宛てて、いつまで

ペイロードに入る一つひとつの項目を クレーム(申し立て)と呼びます。「この札はこう申し立てています」という意味で、検証するまでは申し立てにすぎません。RFC 7519 は、よく使うクレームに3文字の短い名前を決めています。図ではペイロードに寄って、その中身を並べました。

クレーム意味
iss誰が発行したか(発行元)
sub誰・何についての札か(主体)
aud誰に宛てた札か(受け取り手)
expいつまで有効か(この時刻からは受け付けない)
iatいつ発行したか
nbfいつから有効か(この時刻より前は受け付けない)

時刻は、1970年1月1日(協定世界時)からの 秒数 で書きます。図の exp の 1794038400 は、日本時間で 2026年11月7日の 17 時です。JavaScript の Date.now() は ミリ秒 を返すので、そのまま比べると1000倍ずれてしまいます。

どのクレームを必ず入れるかは、JWT を使う仕組みやアプリが決めます。role のような独自の項目を足すこともできます。

  • 誰が、誰について、誰に宛てて、いつまで
  • { "iss": "https://id.example", "sub": "aoi", "aud": "photo-api", "exp": 1794038400, "iat": 1794034800, "nbf": 1794034800 }
  • ← 誰が発行 ← 誰について ← 誰に宛てて ← いつまで ← いつ発行 ← いつから
  • 時刻は 1970年1月1日からの「秒」 1794038400 → 2026年11月7日 17:00(日本時間)
  • JavaScript の Date.now() はミリ秒。1000 で割ってから比べる

印は「ヘッダー.ペイロード」に付ける

第6章で、署名は「何に対して付けたか」が大事だと見ました。JWT では、Base64url にしたヘッダーとペイロードをドットでつないだ文字列、つまり base64url(ヘッダー).base64url(ペイロード) そのものに印を付けます。図では、ヘッダーとペイロードが1本の文字列にまとまり、発行元の鍵を通って紫の署名ができました。

ヘッダーも印の対象に入っている点が大切です。「どの方式で印を付けたか」まで含めて守られるので、途中で誰かがペイロードの exp を1秒延ばしても、ヘッダーの1文字を変えても、計算し直した値は署名と合わなくなります。受け取る側は、届いた文字列から同じ計算をして、三つ目の部分と一致するかを確かめます。

一致すれば、印を付けられる鍵を持つ相手が作った札で、その後だれも書き換えていない、と分かります。言いかえると、守られるのは 改ざんされていないことと発行元 で、中身を隠す働きはありません。前のページで見たとおり、中身は誰でも読めます。

  • 印は「ヘッダー.ペイロード」に付ける
  • ヘッダー
  • .
  • ペイロード
  • 発行元の鍵
  • 鍵
  • 印
  • base64url(ヘッダー) . base64url(ペイロード)
  • 署名
  • 印を付ける対象
  • 1文字でも書き換えると、計算し直した値が署名と合わない

HS256 は共通鍵の MAC、RS256 と ES256 は署名

ヘッダーの alg には、印の付け方の名前が入ります。よく見るのは2種類です。HS256 は、第3章の MAC(共通鍵で作る改ざん検知の値)を HMAC-SHA256 で計算する方式です。図の上の段のように、発行元と受け取る API が同じ共通鍵 K を持ちます。確かめる側も同じ印を作れてしまうので、発行元と受け取り手が同じ組織の中にいて、鍵を安全に分け合える場合に向いています。

RS256(RSA を使う)と ES256(楕円曲線を使う)は、第6章の電子署名です。図の下の段のように、発行元だけが赤い秘密鍵で署名し、受け取る API は青い公開鍵で確かめます。公開鍵では署名を作れないので、たくさんの API に配っても、偽の札を作られる心配はありません。

公開鍵は、JWKS(JSON Web Key Set、RFC 7517)と呼ばれる公開鍵の一覧として、発行元が決まった URL で配ることがよくあります。鍵はときどき入れ替えるので、一覧には複数の鍵が並びます。受け取る側は、JWT のヘッダーにある kid(鍵の ID)を見て、一覧のどの鍵で確かめるかを選びます。

  • HS256 は共通鍵の MAC、RS256 と ES256 は署名
  • 発行元
  • 受け取る API
  • HS256 共通鍵の MAC
  • サーバー
  • サーバー
  • 鍵
  • 鍵
  • 同じ鍵 K を両方が持つ
  • 受け取る側も同じ印を作れる
  • RS256 / ES256 公開鍵で確かめる
  • サーバー
  • サーバー
  • 鍵
  • 鍵
  • 署名付きの JWT
  • JWKS(公開鍵の一覧) kid: k1 / kid: k2
  • kid で選ぶ
  • 公開鍵では署名を作れないので、多くの API に配れる

検証の関門を順に通す

JWT を受け取った API は、中身を使う前にいくつもの関門で確かめます。図では、左の札の前に四つの関門が並び、それぞれに緑の印が付いています。四つすべてを通って、初めて中身を使えます。

一つ目は 形式 で、ドットで3つに分かれ、それぞれ正しくデコードできるかを見ます。二つ目は 署名 です。ここで使う方式と鍵は、ヘッダーに書かれたものではなく、受け取る側があらかじめ決めておいたもの です。三つ目は クレーム で、iss が信頼する発行元か、aud に自分が入っているか、exp を過ぎていないか、nbf より前でないかを確かめます。サーバーどうしの時計のずれに備えて、長くても数分ほどの小さな余裕を持たせることもあります。四つ目は 用途 で、求めている種類の札か、別の目的の JWT が紛れ込んでいないかを見ます。

これを自分で一から書くと、見落としが起こりがちです。広く使われ、検証の手順が確かめられているライブラリを使い、許可する方式・発行元・宛先を設定ではっきり指定します。注意点のまとめは RFC 8725(JWT の推奨事項) にあります。

  • 検証の関門を順に通す
  • 形式
  • 署名
  • クレーム
  • 用途
  • 3つに分かれ デコードできる
  • 決めた alg と 鍵で確かめる
  • iss・aud exp・nbf
  • 想定した 種類と用途か
  • OK
  • OK
  • OK
  • OK
  • JWT
  • 印
  • OK
  • どれか1つでも落ちたら、中身を使わない

ヘッダーの alg を言われるままに信じない

ヘッダーは、札を作った人が自由に書ける場所です。なりすます人が自分で札を作るなら、alg にも好きな値を書けます。受け取る側がその値に従って検証のやり方を選んでしまうと、関門が意味をなさなくなります。図では、なりすます人の作った2枚の札が、どちらも二つ目の関門で止められました。

上の札は alg: none です。JWT の仕様には「印なし」を表す none という値があり、これを素直に受け入れるライブラリでは、署名が空でも通ってしまいます。下の札は、RS256 で確かめるはずの API に alg: HS256 を名乗った札です。古い作りのライブラリには、手元の「鍵」をそのまま使って alg のとおりに計算するものがあり、公開鍵の中身を HMAC の共通鍵として使ってしまいます。公開鍵は誰でも手に入るので、なりすます人は同じ値で MAC を作り、検証を通せてしまいます。この取り違えは「アルゴリズム混同攻撃」と呼ばれます。

防ぎ方はどちらも同じです。受け取る側が「この発行元の札は RS256 で、この鍵で確かめる」と決めておき、それ以外の alg は最初から拒否します。今のライブラリの多くは、許可する方式を指定させる作りになっています。

  • ヘッダーの alg を言われるままに信じない
  • 形式
  • 署名
  • クレーム
  • 用途
  • 3つに分かれ デコードできる
  • 決めた alg と 鍵で確かめる
  • iss・aud exp・nbf
  • 想定した 種類と用途か
  • 人
  • 注意
  • alg: none
  • NG
  • alg: HS256 +公開鍵
  • なりすます人
  • 受け取る側が決めた alg と鍵だけで確かめる

署名が本物でも、宛先が違えば拒否する

アオイが写真アプリを使うために、写真 API 宛ての JWT を受け取ったとします。aud は photo-api で、発行元も署名も本物です。この札がどこかで漏れて、同じ発行元を信頼している決済 API に持ち込まれたら、どうなるでしょう。

署名の関門だけなら、決済 API でも通ってしまいます。同じ発行元が同じ鍵で印を付けているからです。そこで三つ目の関門で aud を見ます。図では、同じ札が上下の二つの API に向かい、宛先が一致する写真 API だけが通しました。決済 API は「この札は自分宛てではない」と分かるので拒否します。

同じように、用途の違う JWT を取り違えない工夫も要ります。たとえば第10章で出てくる ID トークンも JWT ですが、これは「ログインしたのは誰か」をアプリに伝える札で、API を呼ぶための札ではありません。ヘッダーの typ で種類をはっきり書いたり、種類ごとに検証の規則を分けたりして、見た目の似た JWT を混同しないようにします。

  • 署名が本物でも、宛先が違えば拒否する
  • 写真
  • OK
  • 通す
  • 印
  • aud が一致
  • 写真 API
  • aud: photo-api 署名は本物
  • aud が違う
  • カード
  • NG
  • 拒否
  • 決済 API
  • 署名の検証だけでは足りない。aud で宛先を確かめる

ログアウトしても、札は exp まで残る

セッション ID では、サーバーが記録を消せば、その番号はすぐに使えなくなりました。JWT では、受け取る側が印と期限だけで判断するので、発行元で何が起きたかを知りません。図のように、アオイが 16 時 20 分にログアウトしても、exp が 17 時の札は、それまで検証に通り続けることがあります。

しかも JWT はふつう Bearer トークン(持参人トークン)として送られます。Bearer は「持っている人」という意味で、RFC 6750 にあるとおり、札を持っている人なら誰でも使えます。図の盗み見る人が札を手に入れれば、アオイでなくても API を呼べてしまいます。だから JWT は HTTPS で送り、ログや URL に残さないように扱います。

そこで、いくつかの手を組み合わせます。札の exp を数分から数十分と短くして、長く使う分は リフレッシュトークン で新しい札に取り替えます。すぐに止めたい場面では、失効させた札の一覧(失効リスト)と照らし合わせたり、発行元に札の状態を問い合わせたりします。どこまで即座に止めたいかに応じて、サーバーが記録を持つ範囲を選ぶことになります。

  • ログアウトしても、札は exp まで残る
  • 発行 16:00
  • アオイがログアウト 16:20
  • exp 17:00
  • ログアウト後も、検証は通る
  • 人
  • 注意
  • サーバー
  • Bearer の札
  • 盗み見る人
  • API
  • 対策を組み合わせる
  • 短い exp
  • リフレッシュで取り替え
  • 失効リストと照合

role: admin と書いてあれば管理者?

ペイロードに "role": "admin" と書かれた JWT が届いたとします。この人を管理者として扱ってよいでしょうか。答えは、その文字列を見ただけでは決められない、です。図の三つの箱のように、読む・検証する・許可する を分けて考えます。

読む は、Base64url をデコードして中身を見ることです。これは誰にでもできますし、同じ見た目の札なら誰でも作れます。検証する は、決めておいた方式と鍵で署名を確かめ、iss・aud・exp などを確かめることです。ここを通って初めて、「信頼する発行元が、この内容を申し立てた」と言えます。

それでも、まだ許可は済んでいません。発行元が role をどういう意味で書いたのか、自分のアプリで admin に何を許すのかは、第1章で見た 認可 の問題です。発行元が保証している範囲を確かめ、アプリの規則に照らして、操作ごとに許すかどうかを決めます。検証済みの申し立ては、その判断に使う材料です。

  • role: admin と書いてあれば管理者?
  • "role": "admin"
  • 見る
  • 守られている
  • 開いた錠
  • 読む
  • 検証する
  • 許可する
  • デコードすれば 誰でも読める
  • 署名と iss・aud・exp を確かめる
  • アプリの規則で 何を許すか決める
  • 検証済みでも、何を許すかはアプリが決める

まとめ:読む・検証する・許可する

JWT は、中身を書いた札に改ざん防止の印を付けて渡す形式でした。中身は Base64url で書き直されているだけなので、誰でも 読む ことができます。だから秘密は入れず、読めたことを信用の根拠にもしません。

検証する ときは、ヘッダーとペイロードに付いた署名を、受け取る側が決めた方式と鍵で確かめます。HS256 なら共通鍵、RS256 や ES256 なら JWKS で配られた公開鍵を kid で選んで使います。そのうえで iss・aud・exp を確かめ、用途の違う JWT を混同しないようにします。最後に、検証済みの中身を材料にして、何を 許可する かをアプリが決めます。

では、セッション ID をやめて JWT にすれば、サーバーは何も覚えなくてよくなるのでしょうか。答えは「どれだけ早く止めたいか次第」です。JWT は受け取った側が自分だけで検証できるぶん、配ったあとで取り消すのが苦手です。すぐに止めたいなら、短い期限やリフレッシュトークン、失効リストといった仕組みを組み合わせ、必要な分だけ記録を持ちます。

次の章では、JWT がよく運ばれる場面である OAuth を見ます。アオイが、自分の写真へのアクセスを、パスワードを渡さずにアプリへ委ねる仕組みです。

  • まとめ:読む・検証する・許可する
  • JWT = 中身の読める札 + 改ざん防止の印
  • 見る
  • 守られている
  • 開いた錠
  • 読む
  • 検証する
  • 許可する
  • 秘密は入れない 読めても信じない
  • 決めた alg と鍵 iss・aud・exp
  • 検証済みの中身を 材料に決める
  • 次の章:OAuth ― JWT がよく運ばれる「アクセスを委ねる」仕組み

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]