ここまでの記事で、JWT という言葉が何度も出てきました。クライアントクレデンシャルの記事ではアプリが証明書で署名する JWT として、リフレッシュトークンの記事では Entra ID が出すアクセストークンとして登場しています。この記事では、その JWT の中身を、RFC の例を実際にデコードしたり署名を計算し直したりしながら、図にしてみました。
- JWT は「ヘッダー.ペイロード.署名」の3つを「.」でつないだ文字列
- 中身は Base64URL で、暗号ではない。だれでも読める
- 署名があるので、書き換えるとばれる。署名には共通鍵と公開鍵の2つの方式がある
- Entra ID が JWT に署名し、API が公開鍵で確かめるまでの流れ
- Entra ID のアクセストークンに入っているクレーム
まずはたとえで:透明な袋に入った、印鑑つきの書類
JWT(JSON Web Token、「ジョット」と読みます)は、透明な袋に入った、印鑑つきの書類のようなものです。袋は透明なので、中身はだれでも読めます。ただ、書類には発行元の印が押してあり、中身を書き換えると印と合わなくなります。印を押せるのは、印鑑(鍵)を持っている発行元だけです。
大事なのは、JWT は中身を隠すものではなく、中身が本物で書き換えられていないことを示すもの、という点です。この記事ではまず、この「読める」と「書き換えるとばれる」を、RFC の例で確かめます。
どこで使われるか
JWT は、RFC 7519 で決められた形式です。RFC 7519 は JWT を「スペースの限られた場所向けの、コンパクトなクレーム(情報)の表し方」としています。小さな文字列なので、HTTP のヘッダーや URL に入れて運べます。
これまでの記事に出てきたものでは、次の3つが JWT です。
- アクセストークン:Entra ID が出し、アプリが API に見せるトークンです。API は中身を確かめて、応じるかを決めます。
- ID トークン:OpenID Connect で、サインインした利用者の情報をアプリに伝えるトークンです(OpenID Connect の記事でくわしく見ます)。
- client_assertion:アプリが証明書で自分を証明するときに、自分の秘密鍵で署名して送る JWT です(クライアントクレデンシャルの記事の図11)。
なお、OAuth 2.0(RFC 6749)は、アクセストークンの形式を決めていません。どんな形式にするかは認可サーバ次第で、Entra ID は JWT を使っています。
基本の形(RFC):3つの部分を「.」でつなぐ
RFC 7519 に載っている JWT の例です。RFC では、見やすさのために「.」の前後で改行していますが、実際は1行の文字列です。
JWT の例(RFC 7519 の 3.1)
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
1つ目がヘッダー、2つ目がペイロード(中身)、3つ目が署名です。ヘッダーとペイロードは、JSON という形式で書いた文字列を Base64URL という形式に変えたものです。署名は、ヘッダーとペイロードを「.」でつないだ文字列から、鍵を使って計算した値です。
Base64URL は暗号ではない
Base64URL は、データを英数字と一部の記号だけの文字列にする変換です。ふつうの Base64 から、URL で特別な意味を持つ記号を置き換え、末尾の = を付けないようにしたものです。
変換のしかたは決まっていて、鍵も使いません。実際に、図3の JWT を Python で元に戻してみます。
JWT を3つに分けて、Base64URL を戻すスクリプト
import base64 def b64url_decode(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) jwt = ("eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" ".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" ".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") header, payload, signature = jwt.split(".") print(b64url_decode(header).decode()) print(b64url_decode(payload).decode()) print(len(b64url_decode(signature)), "bytes")
実行結果
$ python3 decode.py
{"typ":"JWT",
"alg":"HS256"}
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
32 bytes
鍵がなくても、ヘッダーとペイロードはそのまま読めました。署名は32バイトの値です。ヘッダーの alg にある HS256 は「HMAC と SHA-256 で署名した」という意味で、SHA-256 の出力が32バイトなので、署名も32バイトになります。なお、この例の JSON には改行が入っていますが、これは RFC の例に合わせたもので、JSON として読めれば改行の有無は関係ありません。
ヘッダー:どう署名したか
ヘッダーには、この JWT をどう扱えばよいかが入ります。
| 名前 | 意味 |
|---|---|
| alg | 署名の方式(アルゴリズム)。必ず入れる(RFC 7515)。例:HS256・RS256・ES256 |
| typ | この文字列の種類。入れる場合は JWT にするのが推奨(RFC 7519) |
| kid | どの鍵で署名したかを示すヒント(RFC 7515)。鍵を入れ替えたことを受け取る側に伝えるのにも使う |
ペイロード:クレーム(中身)
ペイロードに入る1つ1つの情報を「クレーム」と呼びます。RFC 7519 は、よく使うクレームの名前を「登録済みクレーム」として決めています。
| クレーム | 意味(RFC 7519) |
|---|---|
| iss | 発行者。この JWT を出した者 |
| sub | 主体。この JWT が何について書かれているか(利用者など) |
| aud | 受け取る相手。この JWT が誰あてか |
| exp | 期限。この時刻以降は受け付けてはならない |
| nbf | 開始。この時刻より前は受け付けてはならない |
| iat | 発行した時刻 |
| jti | この JWT だけの ID |
RFC 7519 では、これらはどれも必須ではありません。どれを使い、どれを必ず確かめるかは、JWT を使う側の仕様で決めます。また、登録済み以外の名前も自由に使えます。図3の例の http://example.com/is_root のように、ほかと重ならない名前(URL の形など)にしておくのがよいとされています。時刻は、1970年1月1日0時(UTC)からの秒数で表します。図3の exp の 1300819380 は、計算すると2011年3月22日18時43分(UTC)です。
署名:書き換えるとばれる
署名の対象は、Base64URL にしたヘッダーと、「.」と、Base64URL にしたペイロードをつないだ文字列です(RFC 7515 でいう JWS Signing Input)。そのため、ヘッダーかペイロードが1文字でも変わると、計算し直した署名は元の署名と合わなくなります。
これも実際に確かめてみます。RFC 7515 の付録には、図3の JWT を作ったときの共通鍵が載っています。その鍵で署名を計算し直し、元の署名と比べます。続けて、ペイロードの joe を bob に書き換えたもので同じことをします。
署名を計算し直して比べるスクリプト(鍵は RFC 7515 の付録 A.1 のもの)
import base64, hashlib, hmac
def b64url_encode(b):
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def b64url_decode(s):
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
key = b64url_decode("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow")
header = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
payload = "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
signature = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
def sign(h, p):
return b64url_encode(hmac.new(key, (h + "." + p).encode(), hashlib.sha256).digest())
print("original:", sign(header, payload) == signature)
forged = b64url_encode(b64url_decode(payload).replace(b"joe", b"bob"))
print("tampered:", sign(header, forged) == signature)
実行結果
$ python3 verify.py original: True tampered: False
元の JWT は署名が一致し(True)、1か所書き換えたものは一致しませんでした(False)。書き換えた人が署名も作り直せば一致しますが、それには鍵が要ります。鍵を持たない人には、署名を作り直せません。
署名の2つの方式:共通鍵と公開鍵
いまの例の HS256 は、共通鍵(両方が同じ鍵を持つ方式)の HMAC を使った署名でした。JWT の署名には、もうひとつ、公開鍵の方式があります(鍵のしくみは暗号の基礎の記事を参照)。
| alg | 方式(RFC 7518) | 鍵 |
|---|---|---|
| HS256 | HMAC と SHA-256 | 共通鍵。発行する側と受け取る側が同じ鍵を持つ。鍵は256ビット以上 |
| RS256 | RSA(PKCS #1 v1.5)と SHA-256 | 秘密鍵で署名し、公開鍵で検証する |
| PS256 | RSA(PSS)と SHA-256 | 同上 |
| ES256 | ECDSA(P-256 曲線)と SHA-256 | 同上 |
| none | 署名なし | なし。既定では受け付けてはならない |
共通鍵の方式では、確かめる側も同じ鍵を持つので、確かめる側も JWT を作れてしまいます。受け取る側が自社の1つのシステムだけ、のような場面に向いています。公開鍵の方式では、確かめる側は公開鍵しか持たないので、JWT を作れません。公開鍵は配ってよいので、受け取る側がたくさんあっても安全です。Entra ID のように、多くの API にトークンを確かめてもらう発行者は、公開鍵の方式を使います。
表の最後の none は「署名なし」です。RFC 7518 は、署名なしの JWT を既定では受け付けてはならないとしています。受け取る側が確かめるときの注意点は、次の記事(JWT の検証)でくわしく見ます。
JWS と JWE:署名つきと暗号化
RFC 7519 では、JWT は JWS(JSON Web Signature、署名つき)か JWE(JSON Web Encryption、暗号化)のどちらかの形で表します。ここまで見てきた3つの部分の形は JWS です。
JWE は中身を暗号化した形で、5つの部分からなります。鍵がないと中身は読めません。一方、よく見るアクセストークンや ID トークンは JWS なので、中身はだれでも読めます。JWT のペイロードには、パスワードのような秘密を入れてはいけません。
誰が何を持つか
ここからは Microsoft の場合です。Entra ID がアクセストークン(JWT)を出し、自社の Web API がそれを確かめる場面を例にします。流れを追う前に、誰が何を持っているかを押さえておきます。
- Entra ID:トークンに署名する秘密鍵を持ちます。秘密鍵は Entra ID の外に出ません。対になる公開鍵は、メタデータに書かれた
jwks_uriの場所で公開しています。鍵は定期的に入れ替わります。 - アプリ:アクセストークンを受け取って API に見せます。Microsoft のドキュメントは、アプリはアクセストークンを「中身の分からない文字列」として扱い、読んだり確かめたりしないよう書いています。中身は API のためのものだからです。
- API:自分のクライアント ID(トークンの
audと比べる値)と、信頼するテナント(issと比べる値)を設定に持ちます。署名の検証には、Entra ID から取ってきた公開鍵を使います。
全体の流れ(Entra ID の場合)
図8のものが、どこで作られ、どこを通って、どこで使われるのかを、一歩ずつ並べます。最初に1回行う「準備」と、トークンを使うたびに行う「実行時」に分けました。番号は2つの図を通しての番号です。
準備(最初に1回):①〜⑨
| 手順 | 誰が・どこで | 何をするか | 鍵(物)はどうなるか・使う鍵 |
|---|---|---|---|
| ① | Entra ID | トークンに署名するための鍵ペアを作る。定期的に入れ替える | 秘密鍵は Entra ID の中だけにある |
| ② | Entra ID | 公開鍵を、jwks_uri の場所で公開する | 公開鍵はだれでも取れる |
| ③ | 管理者・管理センター | API をアプリとして登録する | クライアント ID が決まる(トークンの aud に入る値) |
| ④ | 管理者・サーバで操作 | API の設定に、テナント ID とクライアント ID を書く | ― |
| ⑤ | API → Entra ID | OpenID Connect のメタデータを取りに行く | ― |
| ⑥ | Entra ID → API | メタデータが返る。issuer(発行者)と jwks_uri が入っている | 公開鍵の場所が分かる |
| ⑦ | API → Entra ID | jwks_uri に、公開鍵を取りに行く | ― |
| ⑧ | Entra ID → API | 公開鍵の一覧が返る。鍵ごとに kid が付いている | 公開鍵が API に届く。秘密鍵は渡らない |
| ⑨ | API | 公開鍵をキャッシュする。鍵の入れ替えに備えて、24時間ごとくらいに取り直す | 公開鍵を手元に置く |
実行時(トークンを使うたび):⑩〜⑳
| 手順 | 誰が・どこで | 何をするか | 鍵(物)はどうなるか・使う鍵 |
|---|---|---|---|
| ⑩ | アプリ → Entra ID | トークンを頼む(認可コードフローやクライアントクレデンシャルなど) | ― |
| ⑪ | Entra ID | ヘッダー(alg・kid)と、ペイロード(iss・aud・exp・scp など)を作る | kid に、署名に使う鍵の ID を書く |
| ⑫ | Entra ID | 「ヘッダー.ペイロード」に署名し、3つを「.」でつなぐ | Entra ID の秘密鍵で署名 |
| ⑬ | Entra ID → アプリ | アクセストークン(JWT)を返す | JWT がアプリに届く |
| ⑭ | アプリ → API | Authorization: Bearer ヘッダーに付けて API を呼ぶ。中身は読まない | JWT が API に届く |
| ⑮ | API | 「.」で3つに分け、ヘッダーを Base64URL から戻して読む | ― |
| ⑯ | API | ヘッダーの kid で、⑨でキャッシュした公開鍵から1つを選ぶ | Entra ID の公開鍵を選ぶ |
| ⑰ | API | 署名を検証する | Entra ID の公開鍵で検証 |
| ⑱ | API | iss・aud・exp などを確かめる(次の記事) | ― |
| ⑲ | API | scp や roles を見て、応じるかを決める | ― |
| ⑳ | API → アプリ | 応答(200 とデータ)を返す | ― |
署名は⑫で1回、検証は⑰で1回です。署名は Entra ID の秘密鍵で、検証は API が持つ Entra ID の公開鍵で行います。API は、トークンを受け取るたびに Entra ID に問い合わせる必要はありません。手元の公開鍵だけで、本物か、書き換えられていないかを確かめられます。これが、JWT のような署名つきのトークンを使う大きな理由です。
共通鍵(HS256)の場合:❶〜❼
Entra ID は公開鍵の方式を使いますが、共通鍵の方式(HS256)で JWT を使う場面もあります。たとえば、自社の認証サービスが出した JWT を、自社の API だけが受け取るような場合です。図10と区別するため、番号は黒丸(❶❷…)にしています。
| 手順 | 誰が・どこで | 何をするか | 公開鍵の方式(図9・図10)との違い |
|---|---|---|---|
| ❶ | 管理者 | 共通鍵を作る。256ビット以上にする | 鍵ペアではなく、1つの鍵 |
| ❷ | 管理者 → 発行する側 | 発行する側に鍵を置く | ― |
| ❸ | 管理者 → 受け取る側 | 受け取る側にも、同じ鍵を置く | ⑦⑧では公開鍵だけが動いた。ここでは秘密の鍵が2か所に置かれる |
| ❹ | 発行する側 | HMAC で署名する(alg は HS256) | ⑫と同じ位置。使うのは共通鍵 |
| ❺ | 発行する側 → 受け取る側 | JWT を送る | ― |
| ❻❼ | 受け取る側 | 同じ鍵で署名を計算し直し、届いた署名と比べる | ⑰の代わり。確かめる側も同じ鍵を持つので、JWT を作れてしまう |
❻❼は、さきほど Python で試した手順そのものです。共通鍵の方式は、鍵が2か所にあるぶん、どちらからも漏れないように守る必要があります。同じ HMAC のしくみは、Webhook の署名の記事でも使っていました。
Microsoft(Entra ID)での手順
図9・図10の番号に沿って、Microsoft のドキュメントに載っている例を見ます。
メタデータと公開鍵を取る(図9の⑤〜⑨)
⑤で取りに行く OpenID Connect のメタデータは、次の場所にあります。この URL はどのテナントにも使える共通のものです。1つのテナントのトークンだけを受け付ける API なら、common の部分にテナント ID を入れた URL を使います。
⑤ メタデータの場所(Microsoft のドキュメントの例)
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
⑥で返るメタデータには、公開鍵の場所 jwks_uri が入っています。
⑥ メタデータの jwks_uri(Microsoft のドキュメントの例)
"jwks_uri": "https://login.microsoftonline.com/{example-tenant-id}/discovery/v2.0/keys",
⑦⑧で jwks_uri を開くと、公開鍵の一覧が返ります。公開鍵の形式は JWK(JSON Web Key、RFC 7517)です。
⑧ 公開鍵の一覧(Microsoft のドキュメントの例。値は例のもの)
{
"keys":[
{"kty":"RSA","use":"sig","kid":"A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u","x5t":"A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u","n":"spv...","e":"AQAB","x5c":["MIID..."],"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0"},
{"kty":"RSA","use":"sig","kid":"C2dE3fH4iJ5kL6mN7oP8qR9sT0uV1w","x5t":"C2dE3fH4iJ5kL6mN7oP8qR9sT0uV1w","n":"wEM...","e":"AQAB","x5c":["MIID..."],"issuer":"https://login.microsoftonline.com/{tenantid}/v2.0"},
{"kty":"RSA","use":"sig","kid":"E3fH4iJ5kL6mN7oP8qR9sT0uV1wX2y","x5t":"E3fH4iJ5kL6mN7oP8qR9sT0uV1wX2y","n":"rv0...","e":"AQAB","x5c":["MIID..."],"issuer":"https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0"}
]
}
鍵ごとに kid が付いていて、⑯ではこの kid で鍵を選びます。n と e は RSA の公開鍵の値、x5c は公開鍵を入れた証明書です。Microsoft は、Entra ID が鍵を定期的に入れ替えるので、アプリは鍵の変更に自動で対応できるように作り、24時間ごとくらいに公開鍵を確かめ直すのが妥当としています。
ヘッダーを読んで、鍵を選ぶ(図10の⑮⑯)
Entra ID のアクセストークンのヘッダーは、次のような形です。
⑮ アクセストークンのヘッダー(Microsoft のドキュメントの例)
{
"typ": "JWT",
"alg": "RS256",
"x5t": "H4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3a",
"kid": "H4iJ5kL6mN7oP8qR9sT0uV1wX2yZ3a"
}
alg は RS256 で、公開鍵の方式です。kid が、署名に使った鍵の ID です。x5t は kid と同じ値で、古い v1.0 のトークンにだけ入ります。v2.0 のトークンには kid しか入らないので、鍵を選ぶときは kid を使います。
ペイロードのクレームで判断する(図10の⑱⑲)
署名を確かめたら、ペイロードのクレームを見ます。Entra ID のアクセストークンには、RFC の登録済みクレームのほかに、Microsoft 独自のクレームも入ります。
| クレーム | 中身(Entra ID) |
|---|---|
| iss | トークンを出した Entra ID と、テナント。v2.0 のトークンでは末尾が /v2.0 |
| aud | あて先。v2.0 のトークンでは、いつも API のクライアント ID。合わなければ受け付けない |
| exp・nbf・iat | 期限・開始・認証した時刻(1970年からの秒数) |
| oid | 利用者(またはサービスプリンシパル)の ID。同じテナントなら、アプリが違っても同じ値 |
| sub | 主体の ID。アプリごとに違う値になる |
| tid | テナントの ID |
| azp | トークンを使っているアプリのクライアント ID(v2.0。v1.0 では appid) |
| scp | 任された範囲(スコープ)。人の代わりのトークンだけに入る |
| roles | 与えられた許可(アプリロール)。アプリ自身のトークンでも使う |
| ver | トークンの版(1.0 か 2.0) |
Microsoft のドキュメントは、クレームは値があるときだけ入るもので、あるクレームが必ず入ること、決まった順番で並ぶことを前提にしないよう注意しています。また、name や preferred_username は変わることがあるので、表示だけに使い、判断に使わないよう書いています。
Entra ID のアクセストークンには v1.0 と v2.0 があり、どちらが出るかは、受け取る API の登録(マニフェストの requestedAccessTokenVersion)で決まります。確かめるときは、トークンの ver に合わせて、v1.0 なら v1.0 の、v2.0 なら v2.0 のメタデータを使います。
中身を見るのはデバッグのときだけ
Microsoft は、開発中に中身を確かめるときに限って、jwt.ms のようなサイトで JWT をデコードできるとしています。ただし、次の点に気を付けます。
- アプリは読まない:アプリ(クライアント)は、アクセストークンを中身の分からない文字列として扱い、確かめようとしてはいけません。期限などは、トークンと一緒に返る
expires_inなどで分かります。 - JWT とは限らない:Microsoft のサービス向けのトークンは、JWT としてデコードできないこともあります。Microsoft Graph のトークンは独自の形式なので、この記事の手順では確かめられません。自分の API あてでないトークンを確かめようとしないのが原則です。
- 確かめるのはライブラリで:ASP.NET Core なら Microsoft.Identity.Web が、検証を代わりに行います。署名の検証を自分で書くより、ライブラリを使うのが基本です。
うまくいかないとき
| 起きていること | よくある原因 | 確かめること |
|---|---|---|
| jwt.ms でデコードできない | Microsoft のサービス向けのトークンで、JWT の形ではない | そのトークンの aud が自分の API か。他人の API のトークンなら、読む必要はない |
| 署名の検証で、鍵が見つからない | Entra ID が鍵を入れ替え、キャッシュに新しい kid がない | 公開鍵を取り直す作りになっているか(ライブラリの設定) |
| 発行者(iss)が合わない | v1.0 のトークンを v2.0 のメタデータで確かめている(またはその逆) | トークンの ver と、使っているメタデータの URL |
| あて先(aud)が合わない | Graph あてなど、ほかの API あてのトークンを送っている | アプリが頼んでいる scope が、自分の API のものか |
| 期限の判定がおかしい | 秒数をミリ秒として扱っている、またはサーバの時刻がずれている | exp は1970年からの秒数。サーバの時刻同期 |
企業での例
| 場面 | JWT の使い方 | 押さえどころ |
|---|---|---|
| 社内の Web API を、Entra ID のトークンで守る | API が Entra ID の公開鍵で署名を確かめる | Microsoft.Identity.Web などのライブラリに任せ、aud と iss を正しく設定する |
| トークンの中身を見て、問い合わせを調べる | jwt.ms で、自社 API あてのトークンをデコードする | デバッグのときだけにする。トークンは資格情報なので、チャットやチケットに貼らない |
| 自社の2つのシステムの間で、JWT を受け渡す | HS256(共通鍵)か RS256(公開鍵) | 受け取る側が増えそうなら、最初から公開鍵の方式にしておく |
| JWT に業務の情報を入れたい | ペイロードにクレームを足す | 中身はだれでも読める。秘密や個人情報は入れない |
まとめ
| 項目 | 内容 |
|---|---|
| 形 | ヘッダー.ペイロード.署名 の3つを「.」でつないだ文字列(JWS の場合) |
| Base64URL | 文字の置き換えで、暗号ではない。中身はだれでも読める |
| クレーム | iss・sub・aud・exp・nbf・iat・jti などの情報。RFC ではどれも必須ではなく、使う側が決める |
| 署名 | ヘッダーとペイロードから計算した値。1文字でも変わると合わなくなる |
| 署名の方式 | 共通鍵(HS256)か公開鍵(RS256・ES256 など)。Entra ID は公開鍵の方式 |
| Entra ID の場合 | Entra ID が秘密鍵で署名し、API は jwks_uri から取った公開鍵で検証する。アプリは読まずに運ぶ |
| 注意 | ペイロードに秘密を入れない。署名なし(none)を受け付けない。確かめるのはライブラリで |
参考にした資料
この記事は、次の標準と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。
- RFC 7519: JSON Web Token (JWT)(IETF):JWT の定義、JWS か JWE で表すこと、例(3.1)、登録済みクレームとどれも必須ではないこと(4.1)、自由に決められるクレーム名(4.2・4.3)、NumericDate、typ の推奨値
- RFC 7515: JSON Web Signature (JWS)(IETF):Base64url の定義、署名の対象(JWS Signing Input)、3つの部分のつなぎ方、alg が必須であること、kid の意味、HS256 の例と鍵(付録 A.1)
- RFC 7518: JSON Web Algorithms (JWA)(IETF):HS256・RS256・ES256・PS256・none の意味、HMAC の鍵の長さ、none を既定で受け付けないこと
- RFC 7516: JSON Web Encryption (JWE)(IETF):JWE が暗号化した内容を表すこと、5つの部分
- Access tokens in the Microsoft identity platform(Microsoft Learn):アプリはアクセストークンを読まないこと、jwt.ms はデバッグ用、JWT でないトークンがあること、Graph のトークンは独自形式、API が aud を確かめること、RS256 とヘッダーの例、kid と x5t、メタデータと jwks_uri、公開鍵の一覧の例、鍵の入れ替えと24時間ごとの確認、v1.0 と v2.0 のメタデータ、Microsoft.Identity.Web
- Access token claims reference(Microsoft Learn):ヘッダーとペイロードのクレーム(aud・iss・iat・nbf・exp・oid・sub・tid・azp・scp・roles・ver など)、クレームが必ず入るとは限らないこと、name や preferred_username を判断に使わないこと
あとがき
JWT は見た目が暗号のような文字列なので、なんとなく「中身は守られている」と思っていました。実際に RFC の例を Python で戻してみると、鍵がなくても数行でそのまま読めてしまい、守っているのは中身ではなく「本物かどうか」なのだと実感しました。次の記事では、受け取る側が何をどの順番で確かめるのかを見ていきます。
コメント