JWT を decode したとき、私は claim を上からランダムに読みません。誰が発行したか、誰向けか、今使える時間帯か、subject は誰か、authorization data は何かという順に見ると、authentication と authorization の問題を早く分けられるからです。
通常は JWT デコーダー から始め、registered claims → protocol-specific claims → application-specific claims の順に進みます。
私が最初に読む claims
例えば:
{
"iss": "https://auth.example.com/",
"sub": "user_123",
"aud": "api://orders",
"scope": "orders:read orders:write",
"iat": 1789200000,
"exp": 1789203600
}
なら、私は次の順で見ます。
iss— どの issuer が発行したと主張しているか。aud— どの API / recipient 向けか。exp/nbf— 現在 time window 内か。iat— いつ発行されたとされているか。sub— subject は誰/何か。scope/roles/groups— authorization data は何か。jti/azp/auth_time/acr/amr— protocol 文脈で意味がある追加情報。
iss — issuer
iss は token の issuer を識別します。
{ "iss": "https://login.example.com/" }
私はこれを expected issuer と exact に比較します。ただし decode しただけの iss はまだ untrusted です。
RFC 8725 の重要な点は、単に string が一致するだけでなく、verification に使う key がその issuer に belong している必要があることです。
issuer mismatch を疑うなら JWT デバッガー と issuer validation ガイド で expected / actual / key source を一緒に見ます。
aud — audience
aud は intended recipient を示します。
{ "aud": "api://orders" }
または:
{ "aud": ["api://orders", "api://reporting"] }
Orders API が api://orders を期待しているのに token が api://billing なら、signature が valid でも 401 の説明になります。
環境違いや ID token/access token の取り違えでもよく起きます。詳しくは audience mismatch ガイド を使います。
exp — expiration
exp は token を accept してよい期限です。NumericDate は Unix epoch からの 秒 です。
私は unexpired だから valid と判断しません。exp は time condition の 1 つが通っているだけです。
読みやすい時間に変換するなら JWT 有効期限チェック を使い、詳しい unit / clock skew は JWT exp ガイド で確認します。
nbf — not before
nbf が未来なら、token は expired でなくてもまだ active ではありません。
boundary 近くの failure では verifier と issuer の clock difference も確認します。
iat — issued at
iat は issue time の context として使います。
私は次を見ます。
- 今調べている login session の token か。
- 異常に古くないか。
iat→expの lifetime が想定どおりか。iatが未来になっていないか。
ただし iat だけで universal validity rule を作りません。maximum token age などは application policy です。
sub — subject
sub は token が表す subject です。user ID の場合も service identity の場合もあります。
私は issuer が違う token の sub を global identifier のように混ぜません。多くの system では iss + sub の組み合わせの方が意味があります。
jti — JWT ID
jti は token 個体を識別するために使える値です。
しかし jti があるだけで replay protection / revocation が自動で成立するわけではありません。denylist、state、token profile など application design が必要です。
scope / roles / groups
これらは debugging では非常に重要ですが universal semantics はありません。
{
"scope": "profile orders:read",
"roles": ["support", "billing"],
"groups": ["employees"]
}
platform により scope は space-delimited string だったり、namespaced permission claim を使ったりします。field が roles という名前だから authoritative と決めません。
authentication が成功して 403 になるときはこの層を見ます。
OAuth / OIDC で追加確認する claims
azp— authorized party。auth_time— user authentication time。acr— authentication context class reference。amr— authentication methods references。
私は provider / protocol documentation が意味を定義している場合だけ security decision に使います。generic decoder は value を表示できますが、business rule までは知りません。
どの claim も verify 前は untrusted
私は 3 段階を分けます。
- Decode: payload が何を言っているか。
- Verify: trusted key で signature が正しいか。
- Validate: issuer、audience、time、permissions が current application policy を満たすか。
step 1 から直接「authenticated user」には進みません。
私の claim inspection checklist
- JWT を decode するがまだ trust しない。
issを expected issuer と比較する。audを current API と比較する。exp/nbf/iatを 1 timeline として見る。subを issuer の context で解釈する。- authentication が通った後に scope / role / group を見る。
- trusted issuer に bind された key で signature を verify する。
- provider-specific claims は provider documentation と比較する。
大きな payload を全部読むより、私はまず trust、recipient、time、authorization を説明する claims に集中します。