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

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