fixmint
JA

JWTの中身と、デコードと検証の違い

著 · · 4分で読めます

JSON Web Token(JWT)は、ドットで区切られた3つの文字列、つまりヘッダー、ペイロード、署名からなります。前の2つは符号化されているだけで暗号化されてはいないため、トークンを持っている人なら誰でも中身を読めます。署名によって、サーバーは信頼できる発行元がそのトークンを発行したこと、そして内容が改ざんされていないことを確認できます。トークンをデコードすると、そこに何が書かれているかがわかります。信用してよいかどうかを判断できるのは、検証だけです。

JWTデコーダー期限切れ?それとも有効?無料で使う

3つの部分

トークンはheader.payload.signatureの形をしています。jwt.ioの有名なサンプルトークンの最初の部分は次のとおりです。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

最初の2つの部分はそれぞれ、JSONをbase64urlに変換したものです。base64urlはURLに安全に使える文字列の形式です。最初の部分をデコードすると次のようになります。

{"alg":"HS256","typ":"JWT"}

同じサンプルのペイロードをデコードすると次のとおりです。

{"sub":"1234567890","name":"John Doe","iat":1516239022}

ペイロードには、ユーザーやセッションに関する主張であるクレームが入ります。署名は最初の2つの部分と鍵から計算されるため、1文字でも変わると一致しなくなります。base64urlは鍵がなくても元に戻せるので、パスワード、秘密鍵、個人情報をペイロードに入れてはいけません。

登録済みクレーム

JWTの標準であるRFC 7519は、短いクレーム名をいくつか予約しています。

  • iss(発行者):トークンを作成した主体です。
  • sub(主体):トークンが誰についてのものか示します。通常はユーザーIDです。
  • aud(対象):トークンの想定される宛先となるサービスです。
  • exp(有効期限):この時刻を過ぎると、トークンは無効になります。
  • nbf(有効開始時刻):トークンが有効になる最も早い時刻です。
  • iat(発行時刻):トークンが作成された時刻です。

時刻は、UTCの1970年1月1日からの経過秒数で表し、UNIX時間と呼ばれます。サンプルにはexpがないため、標準の定義では期限切れになりません。実際のトークンには有効期限を設定すべきです。roleやemailなどのその他の名前は、アプリケーションが独自に定義するものです。

デコードは検証ではない

デコードでは、トークンをドットで分割し、base64urlを元に戻して、JSONを読み取ります。これによってトークンが何を主張しているかはわかりますが、偽造されたトークンでも同じように読めます。誰でも任意の署名を付けて、任意のクレームを書けるからです。

検証では、正しい鍵で署名を計算し直して一致するかを確かめます。さらに、expとnbfが現在時刻を挟んでいるか、issとaudがサービスの想定する値かも確認します。検証せずにroleクレームを読んで信頼するアプリは、トークンを書いた人を信じていることになります。これはよくある間違いです。

HS256、RS256、ES256

algの値は署名方式を示します。よく使われるものは3つあります。

  • HS256は、SHA-256によるHMACです。1つの共有秘密鍵で署名と検証の両方を行うため、その秘密鍵はトークンを発行するサーバーの外に出してはいけません。
  • RS256は、SHA-256によるRSA署名です。発行者は秘密鍵で署名し、公開鍵を持つ人は誰でも検証できます。他のサービスは、トークンを作成できなくても検証できます。
  • ES256は、P-256曲線とSHA-256によるECDSA署名です。RS256と同様の仕組みで、鍵と署名のサイズが小さくなります。

1つのサービスが自身のトークンを発行と検証の両方に使う場合はHS256を使います。発行していないトークンを他のサービスが検証する必要がある場合はRS256かES256を使います。

ヘッダーを信用することで生じる問題

ヘッダーはトークンを送る側が書くものなので、検証する側はヘッダーにルールを決めさせてはいけません。

  1. alg: none。 署名のないトークンに"alg":"none"と書けます。かつては、このようなトークンを受け入れて検証を省略してしまうライブラリがありました。許可するアルゴリズムは自分のコードで設定し、トークンから取得してはいけません。
  2. アルゴリズムの混同。 サーバーはRS256を想定し、公開鍵を保持しています。攻撃者は、その公開鍵をHMACの秘密鍵として使って署名したHS256のトークンを送ります。ヘッダーで指定されたアルゴリズムをそのまま受け入れる検証処理は、このトークンを受け入れてしまいます。各鍵は必ず1つのアルゴリズムだけに結び付けてください。
  3. 弱い秘密鍵。 攻撃者はヘッダー、ペイロード、署名を入手できるため、オフラインで候補の秘密鍵を試せます。短い秘密鍵や一般的な単語は、そのようなリストで簡単に突破されます。RFC 7518では、HS256の鍵は少なくとも256ビットであり、かつランダムであるべきとされています。

本番環境のトークンをWebサイトに貼り付ける

有効で期限内のトークンは、期限切れになるまで、それを持つ人なら誰でも使えます。パスワードと同じように扱ってください。サイトに貼り付ける前に、そのサイトがトークンをサーバーに送信するかどうかを確認してください。ブラウザのタブだけで完結するツールであれば、送信する必要はありません。ブラウザの開発者ツールでネットワークリクエストを見れば、どのページでも確認できます。

サーバーが一覧を保持していない限り、トークンは期限切れの前に取り消せないのが一般的です。有効なトークンが漏えいした場合は、サービスにセッションの取り消しや鍵のローテーションを依頼してください。可能であれば、デバッグにはテスト環境のトークンを使ってください。JWTデコーダーは、3つの部分をブラウザのタブ内で読み取ります。貼り付けた内容がどこかに送信されることはありません。

Webhook:共有秘密鍵を使った同じ考え方

多くのWebhookプロバイダーは、リクエスト本文のHMACを、あなたと共有した秘密鍵で計算し、ヘッダーに入れて送ります。受信側も同じHMACを計算して、2つの値を比較します。よくある失敗は細かいものです。ハッシュ化の前にJSONが整形し直されてバイト列が変わってしまう。テスト用の秘密鍵を本番のトラフィックに使ってしまう。あるいは、署名を通常の文字列比較で照合してしまい、処理時間からタイミング情報が漏れるおそれがある。定数時間比較を使ってください。

Webhookの署名検証ツールでは、貼り付けた秘密鍵と本文から署名を計算します。GitHub、Stripe、Slack、Shopifyや、その他任意のHMAC方式に対応しているので、2つの値が一致するかを確認できます。

よくある質問

JWTは暗号化されていますか?

いいえ。標準的なJWTは署名されているだけで、誰でもヘッダーとペイロードを読めます。改ざんを防いでいるのは署名だけです。

ペイロードを編集するとどうなりますか?

署名が一致しなくなるため、検証を行うサーバーはそのトークンを拒否します。クレームを読んだだけでは署名は確認されないので、デコーダーでは編集後の内容がそのまま表示されます。

英語原文をもとに、AIの支援を受けて翻訳しています。

コメント

    まだコメントはありません。最初のコメントを書いてみましょう。

    評価(任意)

    メールアドレスは表示も保存もされません。

    Agentic AI in the Browserの表紙Agentic AI in the Browser

    プライバシーを重視したブラウザ内で動くAIワークフローの活用事例集です。fixmintの基盤となっている、手元のデバイスで処理するという考え方と同じものです。

    $4.99Gumroadで入手英語版