JWTとは?JSON Web Tokenをわかりやすく解説
JWT(JSON Web Token)とは何か、その3つの部分の意味、使用例、そして一般的なトークン運用で避けるべきセキュリティ上の注意点をわかりやすく解説します。

JWTを簡単に説明すると
JWT(JSON Web Token)は、システム間でやり取りできる一連の情報(通常はユーザーやセッションに関するもの)を表す、コンパクトでURLセーフな文字列です。よく eyJ... のように始まる長い値として見かけ、Authorization: Bearer <token> のようなHTTPヘッダーで送られます。
なぜトークンを使うのか?
従来のログインはサーバーセッションに依存することが多い:サインイン後、サーバーがセッションデータを保存し、ブラウザにセッションIDクッキーを渡します。各リクエストにはそのクッキーが含まれ、サーバーはセッションを参照します。
トークンベース認証では、サーバーは全ユーザーの状態を毎回保持する必要がなくなります。代わりにクライアントがJWTのようなトークンを保持してAPI呼び出しに含めます。APIで好まれる理由は:
- 複数サービス間(APIゲートウェイ、マイクロサービス)でうまく機能する
- モバイルやSPAがAPIを直接呼ぶケースに適している
- サーバー間でセッションを共有する必要を減らす
重要な点: "ステートレス"は「サーバー側のチェックが全くない」ことを意味しません。多くのシステムはまだユーザーステータス、鍵ローテーション、取り消し機構による検証を行います。
認証 と 認可(わかりやすく)
- **認証(Authentication)**は「あなたは誰か?」に答えます(サインインして本人性を証明する)。
- **認可(Authorization)**は「何ができるか?」に答えます(請求書を読む、プロジェクトを編集する、管理ページにアクセスする、等)。
JWTは一般に認証の証明(サインイン済みであること)や基本的な認可のヒント(ロール、権限、スコープ)を運びますが、最終的な認可はサーバー側で強制してください。
JWTが使われる場所
JWTはアクセストークンとして以下でよく使われます:
- Web API
- SPA(シングルページアプリ)
- モバイルアプリ
- OAuth 2.0 や OpenID Connect(OIDC)を使うシステム
JWTの構造:ヘッダー、ペイロード、署名
JWTは3つの部分からなるコンパクトな文字列で、各部分はBase64URLエンコードされ、ドットで区切られます:
header.payload.signature
例(省略):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzAwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c…
1) ヘッダー
ヘッダーはトークンの作成方法、特に署名アルゴリズム(例:HS256、RS256/ES256)やトークンタイプを示します。
よくあるフィールド:
typ:多くは"JWT"(実務では無視されることも多い)alg:使用された署名アルゴリズムkid:キー識別子(鍵ローテーション時に検証側が正しい鍵を選ぶのに役立つ)
セキュリティ注意:ヘッダーを鵜呑みにしないでください。実際に使うアルゴリズムの許可リストを強制し、alg: "none" は受け入れないでください。
2) ペイロード
ペイロードはユーザーやトークン文脈に関する“クレーム”(フィールド)を持ちます:誰のためのものか、誰が発行したか、いつ期限が切れるか、など。
重要:JWTはデフォルトで暗号化されていません。 Base64URLエンコードはトークンをURL向けに安全にするだけで、データを隠すものではありません。
そのため、パスワードやAPIキーなどの秘密や、機密の個人データをJWTに入れないでください。
3) 署名
署名はヘッダー+ペイロードを鍵で署名して作られます:
- HS256:共有シークレットで署名・検証
- RS256/ES256:秘密鍵で署名、公開鍵で検証
署名は整合性を提供します:トークンが改ざんされていないこと、信頼できる発行者によって発行されたことを検証できます。機密性は提供しません。
サイズに関する考慮
JWTはヘッダーとペイロードを毎回含むため、トークンが大きいと帯域やオーバーヘッドが増えます。クレームは最小限にし、冗長なデータの代わりに識別子を使ってください。
ペイロードとクレーム:何を入れられて何を入れるべきでないか
クレームは大きく分けて**登録済み(標準名)とカスタム(アプリ固有)**に分かれます。
よく使う登録済みクレーム
iss(issuer):トークンを作った者sub(subject):トークンが誰のものか(多くはユーザーID)aud(audience):トークンの対象(例:特定のAPI)exp(expiration time):トークンを受け入れられなくなる時刻iat(issued at):トークンが作成された時刻nbf(not before):その時刻までは受け入れない
カスタムクレーム:最小限に
受信側が実際に認可判断に必要とするものだけを含めてください。
良い例:
- 安定した内部ユーザー識別子(
user_id) - 小さなロール/権限セット(最新性を保てる場合)
- マルチテナントアプリのテナント/組織ID
プロフィールデータを大量に複製するような「利便性クレーム」は避けてください。トークンが肥大化し、古くなりやすく、漏洩時の影響が大きくなります。
JWTペイロードに絶対に入れてはいけないもの
ペイロードは読み取れるので、次は入れないでください:
- パスワード、APIキー、リフレッシュトークンなどの秘密値
- 支払い情報、政府発行ID、その他の機微な個人データ
- ブラウザ、プロキシ、ログからコピーされたくないもの
機密情報が必要ならサーバー側に保存し、トークンには参照IDだけ入れるか、必要なら暗号化トークン形式(JWE)を使ってください。
署名の仕組み(と何を保証するか)
署名は暗号化ではありません。
- 署名は封かんのようなもの:中身は読めるが、改ざんされていないかを検証できる。
- 暗号化は鍵でロックするようなもの:鍵を持つ者だけが読める。
発行時にサーバーはエンコードしたヘッダー+ペイロードに署名します。後でトークンが提示されたらサーバーは再度署名を計算して比較します。たとえ "role":"user" を "role":"admin" に1文字でも変えれば検証は失敗してトークンは拒否されます。
JWT と OAuth、OpenID Connect、トークンの種類
JWTはトークン形式です。OAuth 2.0 と OpenID Connect(OIDC)はアプリがどのようにトークンを要求・発行・利用するかを定めるプロトコルです。
OAuth 2.0 とアクセストークン/リフレッシュトークン
OAuth 2.0は主に認可に関するもの:アプリがユーザーのパスワードを共有せずにAPIにアクセスすることを可能にします。
- アクセストークン:APIに提示して権限を証明する;JWTでも不透明トークンでもあり得る
- リフレッシュトークン:新しいアクセストークンを得るための長期間有効なトークン
アクセストークンは一般に短命(数分)です。短い寿命は漏洩時の被害を限定します。
OpenID Connect(OIDC)とIDトークン
OIDCはOAuth 2.0に認証を追加し、通常JWTであるIDトークンを導入します。
- IDトークン:クライアントアプリがユーザーの本人性を確認するためのもの
- アクセストークン:APIを認可するためのもの
重要ルール:IDトークンをAPI呼び出しに使ってはいけません。
実装フローの詳細は /blog/jwt-authentication-flow を参照してください。
よくある JWT 認証フロー
典型的な流れは次の通りです:
1) ログイン
ユーザーがサインイン(メール/パスワード、SSOなど)します。成功するとサーバーは sub や exp のような必要なクレームを含むJWT(多くはアクセストークン)を作成します。
2) トークン発行
サーバーはトークンに署名してクライアント(Webアプリ、モバイルアプリ、別サービス等)に返します。
3) API呼び出し
保護されたエンドポイントに対してクライアントはJWTを Authorization ヘッダーに入れて送ります:
Authorization: Bearer <JWT>
4) 検証
APIはリクエストを提供する前に通常次を確認します:
- 署名(整合性+信頼できる発行者)
exp(有効期限切れでないこと)iss(期待する発行者)aud(自分のAPI向けであること)
すべてのチェックが通ればAPIはユーザーを認証済みとして扱い、認可ルール(レコード単位の権限など)を適用します。
5) クロックスキューについての注意
システムの時計はずれることがあるため、exp や nbf の検証時に小さなクロックスキューを許容することが多いです。スキューは小さく保って、トークン有効期間を実際より延ばさないようにしてください。
JWTを安全に保存する場所
保存方法によって攻撃者がトークンをどう入手できるか、再利用のしやすさが変わります。
ブラウザアプリ:メモリ vs localStorage vs クッキー
**メモリ(SPAで推奨されることが多い)**はアクセストークンをJSの状態に保持します。リロードで消え、後から抜かれるリスクは減りますが、XSSがあると実行時に読まれてしまいます。短命トークンとリフレッシュの流れと組み合わせてください。
localStorage/sessionStorageは扱いやすいですが危険です:XSSがあればトークンを抜かれてしまいます。使うならXSS対策を徹底し、トークンを短命にしてください。
Secureなクッキー(多くの場合ウェブで最も安全なデフォルト)は HttpOnly にしてJSから読めなくします—XSSによる窃取リスクを減らせますが、ブラウザが自動で送るためCSRFリスクが出ます。
クッキーを使うなら:
HttpOnlySecure(HTTPSのみ)SameSite=LaxまたはSameSite=Strict(クロスサイトフローでSameSite=None; Secureが必要になる場合もある)
状態変更リクエストにはCSRFトークンを検討してください。
モバイルアプリ:OSのセキュアストレージを優先
iOS/AndroidではKeychainやKeystoreといったOSのセキュアストレージにトークンを保存してください。プレーンなファイルや設定に保存するのは避けてください。ルート化/脱獄された端末を脅威モデルに含めるなら抽出可能と想定し、短命トークンとサーバー側の制御に頼ってください。
最小権限の原則
トークンにできることを制限してください:スコープ/クレームは最小限に、アクセストークンは短命に、機密データは埋め込まないでください。
避けるべき一般的なJWTセキュリティの落とし穴
JWTは便利ですが、よくあるミスでインシデントが発生します。トークンは現金のように扱ってください:入手した者は通常それを使えます。
1) 有効期限が長すぎる
トークンの寿命が数日・数週間あると、漏洩時の被害がその間続きます。
アクセストークンはできるだけ短命(数分)にして、より安全な仕組みで更新することを推奨します。「ログイン状態を保持する」機能はリフレッシュトークン+サーバー側制御で実装してください。
2) iss と aud のチェックを省く
署名が正しくてもそれだけでは不十分です。iss と aud を検証し、時間に関するクレーム(exp、nbf)も検証してください。
3) デコードしたペイロードを信用する
デコードは検証ではありません。必ずサーバーで署名を検証し、権限もサーバー側で強制してください。
4) アルゴリズム混乱や鍵の取り扱いミス
- トークンが主張するアルゴリズムを無批判に受け入れないでください。期待するアルゴリズムのみを許可してください。
- 対称鍵(HS256)と公開鍵/秘密鍵(RS256/ES256)を混同しないでください。
- 環境ごとに鍵を分け、鍵ローテーションを行って被害範囲を小さくしてください。
5) URLやログ、リファラーにトークンを漏らす
クエリパラメータにJWTを入れるのは避けてください。ブラウザ履歴、サーバーログ、解析ツール、リファラーヘッダーに残る可能性があります。
代わりに Authorization: Bearer ... を使ってください。
6) 鍵ローテーションや取り消しの計画がない
鍵やトークンが漏れることを前提に計画してください。署名鍵をローテーションし、kid を使ってスムーズに切り替えられるようにし、取り消し戦略(短命トークン+高リスク時の拒否リストやアカウント無効化)を用意してください。保存方法の詳細は /blog/where-to-store-jwts-safely を参照してください。
JWTを使うべき時(と使うべきでない時)
JWTは有用ですが自動的に最適解というわけではありません。重要なのは、毎回データベース照会なしに検証できる自己完結型トークンの利点があるかどうかです。
JWTが適しているケース
- 大規模なステートレスAPI:署名+有効期限でローカル検証でき、毎回のセッション照会が不要
- 複数サービス/マイクロサービス:共通の検証ルールと公開鍵で検証可能
- SPAやモバイルアプリ:クライアントが直接APIを呼ぶ場合
- 短命アクセストークン:窃取時の影響が小さい
JWTが不向きなケース
- 即時取り消しが必須:全端末で今すぐログアウトさせたい場合はセッションが簡単
- トークンに機密データを入れる必要がある:通常のJWTは署名のみで暗号化はしない
- 長期間有効なトークンが必要:価値が高く窃取リスクが大きい
単純なセッションCookieが良い場合
伝統的なサーバーレンダリングのWebアプリで即時無効化が重要な場合、サーバー側セッション + HttpOnlyクッキーが単純で安全なデフォルトになることが多いです。
早見チェックリスト
ステートレスな検証がサービス間で必要で、トークンを短命にできるならJWTを選んでください。
取り消しを即時に行いたい、トークンに機密データを入れる必要がある、セッションCookieで問題なく運用できるならJWTは避けてください。
実践的チェックリストとFAQ
検証チェックリスト(毎回チェックすべき項目)
- 署名が有効
正しい鍵と期待するアルゴリズムで検証し、無効な署名は例外なく拒否してください。
exp(有効期限)
トークンが期限切れでないことを確認してください。
nbf(有効開始)
存在する場合、トークンがまだ早すぎないか確認してください。
aud(オーディエンス)
トークンが自分のAPI/サービス宛であることを確認してください。
iss(発行者)
期待する発行者からのものか確認してください。
- サニティチェック(推奨)
トークン形式の検証、最大サイズの強制、予期しないクレーム型の拒否などでエッジケースを減らしてください。
HS256 と RS256/ES256 の選び方
-
HS256(対称鍵):ひとつの共有シークレットで署名・検証
- 適する場面:単一チームが管理する単一アプリ/API
- 注意点:検証者がシークレットを持つとトークンを発行できてしまう
-
RS256 / ES256(非対称鍵):秘密鍵で署名し、公開鍵で検証
- 適する場面:複数のサービスがトークンを検証する場合;公開鍵を配布しても署名はできない
- 運用メモ:署名者だけが秘密鍵を持つためローテーションが安全になりやすい
経験則:独立した複数のシステムが検証を行うなら(または検証側を完全には信頼できないなら)RS256/ES256を選んでください。
ログと監視(トークンを漏らさずに)
- 生のトークンをログに残さない(ヘッダー、クッキー、クエリ文字列を含む)。
- 相関のためにログを取りたい場合はトークンのフィンガープリント(ハッシュ)や安全なメタデータ(
iss、aud、ユーザーIDはポリシー次第)をログに残す。 - 異常を監視する:署名失敗の増加、期限切れトークンのスパイク、想定外のaud/iss、疑わしいリフレッシュパターンなど。
FAQs
JWTは暗号化されていますか?
デフォルトでは暗号化されていません。ほとんどのJWTは署名されており、暗号化されていません。内容はトークンを持つ誰でも読めます。機密性が必要ならJWEを使うか、JWTに機密データを入れないでください。
JWTを取り消せますか?
自己完結型のアクセストークンだけに頼ると簡単には取り消せません。一般的な対策は短命アクセストークン、重大イベント用の拒否リスト、リフレッシュトークンのローテーションなどです。
exp はどれくらいがよいですか?
UXとアーキテクチャで可能な限り短くしてください。多くのAPIはアクセストークンを数分に設定し、リフレッシュトークンで長いセッションを維持します。
Koder.ai で JWT 保護されたアプリを速く作る
新しいAPIやSPAでJWT認証を実装する際、多くはミドルウェアの配線、iss/aud/exp の検証、クッキー設定、ログにトークンを出さないようにする、といった繰り返しの作業です。
Koder.ai を使えば、チャット駆動のワークフローでWebアプリ(React)、バックエンド(Go + PostgreSQL)、あるいはFlutterモバイルアプリを素早く作り、プランニングモードやスナップショット/ロールバックでセキュリティ設定を洗練し、準備ができたらソースコードをエクスポートできます。検証ロジック、鍵ローテーション戦略、デプロイ設定(カスタムドメイン含む)を管理しながら実装を加速する実用的な方法です。
よくある質問
JWTとは何で、通常どこに送りますか?
JWT(JSON Web Token)は、クレーム(データフィールド)を運び、サーバーで検証可能なコンパクトでURLセーフな文字列です。通常はAPIリクエストで次のように送ります:
Authorization: Bearer <token>
ポイントは、サーバーがトークンの署名によって整合性を検証でき、毎回ユーザーごとのセッションレコードを照会せずに認証を行えることです。
JWT認証はサーバーセッションとどう違いますか?
セッション認証は通常サーバー側に状態を保存します(クッキー/セッションIDで参照するセッションレコード)。JWTベースの認証では、クライアントが各リクエストで署名付きトークンを提示し、APIがそれを検証します。
JWTは、検証をローカルに行えるため、APIやマルチサービス環境でよく使われます。
「ステートレス」と言っても、取り消しリスト、ユーザー状態チェック、鍵ローテーションなどサーバー側のチェックを行うことは多いです。
JWTの3つの部分(ヘッダー、ペイロード、署名)とは何ですか?
JWTはドットで区切られた3つのBase64URLエンコード部分から成ります:
header.payload.signature
ヘッダーは署名方法を説明し、ペイロードはsubやexp、audのようなクレームを含み、署名は改ざん検知を可能にします。
JWTは暗号化されていますか?中身は読めますか?
いいえ。標準的なJWTは通常署名されており、暗号化されていません。
- 署名は整合性(改ざんされていないこと)と発行者の正当性を証明します。
- トークンを取得した誰でもヘッダーとペイロードをBase64URLデコードして読めます。
機密性が必要な場合はJWE(暗号化トークン)を検討するか、機密データはサーバー側に置き、JWTには識別子だけを入れてください。
JWTの署名は何を保証し、何を保証しませんか?
署名は、トークンが改ざんされておらず、署名鍵を持つ発行者が作成したことを検証できます。
しかし署名は次を保証しません:
- ペイロードの内容を隠すこと
- ユーザーがまだ有効であること(サーバー側で確認する必要がある)
expより前に自動的に取り消されること
トークンは有効期限まで誰でも再利用できる「資格情報」として扱ってください。
JWTヘッダーの`alg`と`kid`は何で、なぜ重要ですか?
algはどのアルゴリズムで署名されたかを示し、kidは鍵識別子で鍵ローテーション時に正しい鍵を選ぶのに役立ちます。
セキュリティ上の注意:
- 期待するアルゴリズムだけを許可する(allowlist)こと。任意の
algを受け入れない。\n-alg: "none"は受け入れない。\n- 信頼できないkidが原因で安全でない鍵探索を行わせないこと。
ペイロードにどのようなクレームを含めるべきですか?
まず標準の登録済みクレームを使い、カスタムクレームは最小限にしましょう。
よく使う登録クレーム:
iss(発行者)sub(サブジェクト/ユーザー識別子)aud(対象オーディエンス/想定API)exp(有効期限)iat(発行時刻)nbf(有効開始時刻)
ペイロードは読み取れるので、秘密や機密個人情報は入れないでください。必要ならサーバー側に保存し、トークンには参照IDだけ入れます。
JWT、OAuth 2.0、OpenID Connectの関係は?(アクセストークンとIDトークン)
JWTはトークン形式であり、OAuth 2.0やOpenID Connectはトークンの要求・発行・使用方法を定めたプロトコルです。
典型的な対応:
- アクセストークン:API呼び出しに使う(JWTまたは不透明トークン)
- IDトークン(OIDC):クライアントがユーザーの本人確認に使う(通常JWT)
- リフレッシュトークン:新しいアクセストークンを得るために使う(通常不透明、機密性が高い)
重要:IDトークンをAPI呼び出しに使ってはいけません。
ブラウザアプリではどこにJWTを安全に保存すべきですか?
ブラウザアプリでは主に次の選択肢があります:
- メモリ:SPAsで推奨されることが多い。リロードで消え、長期間の窃取リスクは減るが、実行中のXSSで読まれる可能性はある。短命トークン+リフレッシュで補う。
- localStorage/sessionStorage:扱いやすいがXSSで簡単に抜かれる。使うならXSS対策(CSP、出力エスケープ、依存関係管理)を厳格にして、トークンを短くする。
- HttpOnlyかつSecureなクッキー:JSから読めないためXSSによる窃取リスクが下がるが、ブラウザが自動で送るためCSRF対策が必要になる。
クッキーを使う場合は HttpOnly、Secure、SameSite=Lax|Strict を設定し、状態変更リクエストにCSRFトークンを検討してください。
どの場合でも、アクセストークンを短命にして権限を最小化してください。
APIがJWTを検証するときに行うべきチェックは何ですか?
APIは最低でも次を検証すべきです:
- 署名(正しい鍵と許可されたアルゴリズムで)
exp(有効期限)iss(期待する発行者)aud(自分のAPI向けか)nbf(あれば有効開始前でないか)
運用上の追加ガード:最大トークンサイズの上限を設ける、予期しないクレーム型を拒否する、小さなクロックスキューを許容して時間差を吸収する、など。