JWT ガイド

秘密鍵なしで JWT をデコードするときに私が確認すること

秘密鍵や署名シークレットがなくても JWT の header と payload を読める理由、どこまで確認できるか、どこから署名検証が必要かを実務的に整理します。

著者: JWTDecoder.tools最終確認 2026-09-13

編集上の表現について:一人称の記述は JWTDecoder.tools の検証済みワークフローを示すもので、発行者・組織著者は JWTDecoder.tools です。

secret なしで JWT をデコードし署名検証前で止める流れ
まずデコードして確認し、真正性の判断は信頼済み鍵で署名検証してから行います。

JWT を受け取って「まず中身を見たい」と思ったとき、私は最初から signing secret や private key を探しません。普通の 3 セグメントの署名付き JWT/JWS なら、header と payload は暗号化ではなく Base64URL でエンコードされているためです。つまり、読むだけなら秘密鍵は不要です。

ただし、ここで最も大事なのは、読めたことと信頼できることを分けることです。私は常に decode = 内容の確認、verify = 署名の真正性確認、validate = このアプリが受け入れてよいかの判定 と分けています。

すでにトークンが手元にあるなら、まず JWT デコーダー で header、payload、時刻 claims、signature segment を確認します。

最初に 3 セグメントか 5 セグメントかを見る

最初に compact token を . で分けます。

通常の署名付き JWT/JWS は次の形です。

header.payload.signature

一方、compact JWE は通常 5 セグメントです。5 セグメントなら、私は「Base64URL を戻せば claims が見えるはず」と考えるのをやめます。JWE は暗号化されているので、デコードと復号は別の処理です。

この確認だけで、eyJ... で始まる文字列をすべて同じ JWT として扱うミスをかなり減らせます。

なぜ secret なしで header と payload を読めるのか

署名付き JWT の header と payload は JSON を Base64URL で表現したものです。Base64URL はデータを隠す暗号ではありません。

例えば header が次のように読めることがあります。

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key-2026-09"
}

payload は次のような形です。

{
  "sub": "user_123",
  "iss": "https://auth.example.com/",
  "aud": "api://backend",
  "scope": "read:user write:profile",
  "iat": 1789000000,
  "exp": 1789003600
}

ここまで読むのに secret は不要です。署名鍵が守るのは claims の機密性ではなく、改ざん検知と真正性 です。そのため私は payload にパスワードや秘密情報を入れない前提でシステムを設計します。

デコード直後に私が見る順番

全部の claim を同じ重みで見るのではなく、次の順に確認します。

  1. alg — どの署名アルゴリズムを名乗っているか。
  2. kid — 後でどの鍵を選ぶ手掛かりになるか。
  3. iss — どの issuer が発行したと主張しているか。
  4. aud — どの API / recipient 向けか。
  5. exp / nbf / iat — 時間条件に矛盾がないか。
  6. scope / roles / groups — authorization data は何か。
  7. sub — subject は誰か、または何か。

この段階では、これらはすべて「トークンがそう主張している」情報です。まだ本人確認には使いません。

読めた payload をそのまま信用しない

攻撃者でも JSON を作り、Base64URL にして JWT らしい文字列を作れます。

例えば payload を:

{ "role": "user" }

から:

{ "role": "admin" }

へ変更しても、私はその改変版をデコードできます。変わるのは署名対象の bytes なので、正しく設定された verifier なら元の signature は通らないはずです。

だから私は UI でもログでも「decoded successfully」を「valid JWT」と同義にしません。

どの時点で secret や public key が必要になるか

質問が「何が入っているか」から「信頼する signer が本当にこの bytes に署名したか」に変わった瞬間です。

HS256 なら共有 secret が必要です。RS256 なら通常 verifier が必要とするのは issuer の public key で、private signing key ではありません。

信頼済みの鍵がある場合は JWT 署名検証 を使います。JWKS を使う環境なら JWKS Validator で kid と公開鍵を対応させます。

decode / verify / validate を分ける理由

私はこの 3 段階を混ぜません。

Decode

「この token は何を主張しているか」を読みます。header、claims、timestamps、signature segment の存在を確認します。

Verify

「信頼済み key と照合して cryptographic signature が正しいか」を確認します。鍵の入手元まで含めて trust boundary です。

Validate

「この API が受け入れる token か」を確認します。iss、aud、exp、nbf、token type、scope、role などをアプリの policy と比較します。

この区別があると、Base64URL が読めただけで authentication 成功と誤認しません。

有効期限らしい問題があるとき

exp がある場合、JWT NumericDate は Unix epoch からの 秒 です。JavaScript の Date.now() はミリ秒なので、そのまま比較しません。

時間だけ見たい場合は JWT 有効期限チェック で iat、nbf、exp を一緒に確認します。

exp が未来なのに 401 なら、私は expiration に固執せず issuer、audience、signature に進みます。

production bearer token の扱い

payload が読めるからといって bearer token の価値が低いわけではありません。私は production access token を credential として扱います。

実務では次を守ります。

  • JWT を URL query に入れない。
  • full bearer token をログに残さない。
  • synthetic / redacted token で再現できるなら production token を使わない。
  • refresh token、private key、HMAC secret をデバッグ用メモに貼らない。
  • デコードだけのために token をサーバーへ送らない。

JWTDecoder.tools の decoder も browser memory 内で処理する設計です。ただし browser extension、malware、clipboard tool など端末上の別ソフトまで防げるわけではありません。

私の最終チェックリスト

秘密鍵なしで JWT を調べるときは、最後に次を確認します。

  1. 3 セグメント JWS か、5 セグメント JWE か。
  2. header と payload が Base64URL / UTF-8 JSON として読めるか。
  3. alg、kid、iss、aud、time claims は何を示しているか。
  4. decoded claims はまだ untrusted と扱っているか。
  5. 時間問題なら JWT 有効期限チェック を使ったか。
  6. authenticity が必要なら JWT 署名検証 または JWKS Validator に進んだか。
  7. API が拒否するなら JWT デバッガー で issuer / audience を比較したか。

私が実務で使う結論はシンプルです。通常の署名付き JWT は秘密鍵なしで読めます。しかし、その readable claims を信頼するには、必ず信頼済み key と application policy に戻って verification / validation を行います。

出典・参考資料

関連ガイド