← 記事一覧

JWT をデコードしてクレームを読む方法

JWT は 3 つの Base64url セクション。JavaScript と Python でヘッダとペイロードをデコードする方法 —— そしてトークンをデコードすることは検証することと同じではない理由。

JWT(JSON Web Token)はドットで連結された 3 つの Base64url エンコード部分です。クレームを読むためのデコードは 1 回の関数呼び出しで済み、秘密鍵は不要 です —— そのため JWT のペイロードは機密データを置く場所ではありません。本ガイドでは JWT とは何か、各部分のデコード方法、そしてデコード検証の決定的な違いを説明します。

JWT とは?

JWT はコンパクトで URL セーフなトークンで、当事者間でクレーム —— 通常はユーザの ID と権限 —— を運びます。RFC 7519 で標準化されています。トークンはこのように見えます:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFkYSJ9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

2 つのドットで 3 つの部分に分割されます:headerpayloadsignature

3 つの部分

  • Header —— 署名アルゴリズムを記述する小さな JSON オブジェクト、例:{"alg":"HS256","typ":"JWT"}
  • Payload —— claims の JSON オブジェクト。例:sub(subject)、exp(有効期限)、その他のカスタムフィールド。
  • Signature —— header と payload に対する暗号署名で、改竄検出のためのもの。人間に読めず、JSON でもありません。

header と payload は Base64url エンコードされた JSON です。署名は JWS(RFC 7515)で生成されます。

JavaScript で JWT をデコード

ドットで分割し、最初の 2 つを Base64url デコード。JWT はBase64url+/ の代わりに -_、パディングなし)を使うので、デコード前に変換が必要です:

function decodeJwt(token) {
  const [headerB64, payloadB64] = token.split('.');

  const decode = (b64url) => {
    const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
    const json = atob(b64);
    return JSON.parse(json);
  };

  return { header: decode(headerB64), payload: decode(payloadB64) };
}

Unicode を完全に扱うには、Base64 をバイトにデコードしてから TextDecoder を通してください。atob だけではいけません。

Python で JWT をデコード

import base64, json

def decode_jwt(token):
    header_b64, payload_b64, _sig = token.split('.')
    def decode(part):
        padded = part + '=' * (-len(part) % 4)
        return json.loads(base64.urlsafe_b64decode(padded))
    return decode(header_b64), decode(payload_b64)

デコードは検証ではない

JWT で最も重要な点です。誰でも トークンをデコードして payload を読めます —— 鍵不要。デコードはトークンが主張することを示すだけで、その主張が信用できるかを示しません。

トークンを信頼するには、信頼できるライブラリを使い、サーバの秘密鍵または公開鍵に対して署名を検証 しなければなりません —— 自作しないこと:

import jwt from 'jsonwebtoken';

// 署名検証 AND 有効期限チェック;無効なら投げる
const claims = jwt.verify(token, process.env.JWT_SECRET);

// jwt.decode(token) —— 検証しない;確認のみ

サーバでの検証こそが JWT を認証情報にします。デコード済み payload を検証なしに信頼するのは古典的な認証バグです。

JWT ペイロードは暗号化されていない

payload は単に Base64url エンコードされた JSON なので、機密性はゼロ です。これは Base64 は暗号化ではない と同じ誤解です:エンコードは誰でも逆にできます。

パスワード、完全なクレジットカード番号、機密の個人情報を JWT payload に入れてはいけません。本当に暗号化されたトークンが必要なら、JWE(JSON Web Encryption、RFC 7516)を使ってください —— 別の、はるかにマイナーな標準です。実トークンを信頼できないオンラインデコーダに貼るのも避けてください;機密 JSON をオンラインツールに貼るべきでない理由

検証順序:署名が先、クレームは後

本番でトークンを検証するとき、順序は重要です。安全なチェックリスト:

  1. アルゴリズム許可リスト —— 期待集合(例:['RS256'])以外は拒否。none は決して受け入れない。
  2. 署名 —— 正しい鍵で検証(JWKS の kid から検索)。
  3. exp —— トークンが期限切れでないこと。
  4. nbf(not-before)—— 使用が早すぎないこと。
  5. iat —— 発行時刻が異常に未来でないか整合性チェック(時計ずれは別)。
  6. iss —— 発行者が期待する信頼できる権威であること。
  7. aud —— audience があなたのサービスを含むこと。

ほとんどの JWT ライブラリは 1-4 をデフォルトで行い、6-7 はオプトインを要求します。issaud がチェックされない限り、エコシステム内の別のサービス向けの妥当なトークンがあなたのサービスにリプレイされ得ます。

標準的なクレーム

Claim意味
issIssuer —— トークンの発行者
subSubject —— トークンの対象者(多くはユーザ ID)
audAudience —— トークンの宛先
expExpiry —— この Unix タイムスタンプ後は無効
iatIssued-at —— トークン発行時の Unix タイムスタンプ
nbfNot-before —— この時刻まで無効

kid ヘッダと alg: none の落とし穴

実際の JWT に頻出する 2 つのヘッダフィールド、すぐに見分けられるようにしましょう:

  • kid(key ID) —— 鍵集合(JWKS)のどの鍵がトークンを署名したかを示すヒント。検証者が署名チェック前に正しい鍵を選べるようヘッダに置かれます。kid は信頼できない入力として扱ってください:固定の許可リスト(または取得済み JWKS)で検索し、ファイルパス、SQL パラメータ、URL として使わないでください。
  • alg: "none" —— 妥当だが歴史的に危険なアルゴリズムで「署名なし」を意味します。デコーダは喜んでデコードし、寛容な検証者は受け入れてしまい、偽造ペイロードを「妥当」なトークンにします。明示的に alg: none を拒否し、ライブラリは期待する正確なアルゴリズム(例:['RS256'])に固定して、トークン自身のヘッダ申告を信用しないでください。

ブラウザで JWT をデコード

トークンのクレームを素早く検査するには、任意の Base64url 部を fixjson の Base64 デコーダ に貼り —— ローカルで JSON にデコードし、サーバに送信しません。機密でないトークンには専用のデバッガでもよいですが、実ユーザデータを含むものにはローカル優先のツールが安全です。

よくある質問

JWT をデコードするには?

ドットで分割し、最初の 2 部分(header と payload)を Base64url デコードして JSON にします。第 3 は署名で人には読めません。デコードに鍵は不要です。

JWT のデコードと検証は同じですか?

違います。デコードはクレームを読むだけで誰でもできます。検証は鍵で署名をチェックしてトークンが本物で改竄されていないことを確認します。トークンを信頼する前に必ずサーバで検証してください。

JWT のデータは誰でも読めますか?

はい。payload は Base64url エンコードであって暗号化ではありません —— トークンを持つ誰にとっても平文です。秘密を JWT payload に保管しないでください。Base64 は暗号化ではない

JWT、JWS、JWE の違いは?

署名付き JWT は JWS(RFC 7515)を使います —— payload は読めて改竄検出可能。JWE(RFC 7516)は実際に payload を暗号化して機密性を提供。日常で見る普通の JWT は JWS です。

kid クレームは何に使う?

kid はヘッダフィールド(payload クレームではない)で、JWKS のどの鍵がトークンを署名したかを示し、検証者が正しい鍵を選べるようにします。あなたが制御する固定鍵集合で検索 —— ファイルパスや URL として信用しないでください。

なぜ alg: "none" は危険?

「署名なし」を意味します。トークンのアルゴリズム宣告を尊重する寛容な検証者は、偽造ペイロードを受け入れます。ライブラリを期待する正確なアルゴリズム(例:['RS256'])に固定し、none を明示的に拒否してください。

トークンをローカルで検査