JWT(JSON Web Token)はステートレスな認証で広く使われる一方、検証の実装を誤ると認証を丸ごと回避される脆弱性です。 対策の要は、署名アルゴリズムを固定して検証し、トークンを失効できる設計にできているかどうかです。
JWT認証の脆弱性とは
JWT(JSON Web Token):ヘッダ、ペイロード、署名の3つをつないだトークンで、サーバが署名を検証することで改ざんされていないことを確認します。 検証処理の実装ミスや、トークンの扱い方の誤りが脆弱性の原因になります。
署名検証を正しく行えていないと、攻撃者がペイロードのuser_idやroleを書き換えても検知できません。
その結果、他人へのなりすましや管理者権限への昇格を許してしまいます。
JWTはサーバ側に状態を持たないため、発行後のトークンは失効の仕組みがなければ有効期限まで止められず、漏洩時の被害が長引きます。
JWT認証の脆弱性が起きるパターン
典型的な弱点は次のとおりです。
alg: noneを許可し、署名なしのトークンを有効なものとして受け入れてしまう- そもそも署名を検証せず、ペイロードをデコードするだけで信用している
- 公開鍵方式(RS256)を想定した検証で、攻撃者が
algをHS256に変え、公開鍵を秘密鍵として署名を通す(アルゴリズム混同) - 署名鍵が短く単純で、総当たりで割り出される
- 有効期限や失効の仕組みがなく、一度発行したトークンを無効化できない
- パスワードや個人情報をペイロードに入れ、デコードだけで読まれてしまう
共通するのは「トークンの署名を正しく検証できていないか、発行後のトークンを制御できていない」という点です。
対策法
原則は「受け入れる署名アルゴリズムを固定して検証し、トークンは短命にして失効できるようにする」ことです。
- サーバ側で許可するアルゴリズムを明示的に固定し、トークンの
algを信用しない。noneは拒否する。これが本命の対策になる - 署名鍵は十分な長さと乱数性を持たせ、公開鍵方式では検証に公開鍵だけを使う
- アクセストークンは短命にし、リフレッシュトークンと失効リスト(またはセッション)で無効化できる設計にする
- ペイロードには機密情報を入れず、識別子や権限など最小限にとどめる
- 実績のあるライブラリの検証機能を使い、自作の検証処理を避ける
署名検証とアルゴリズム固定は認証の土台であり、まずここが正しいかを確認するのが最優先です。 そのうえで短命化と失効設計を加えるのが、被害を長引かせないための費用対効果の高い順序になります。
Rails での事例
Ruby の jwt gem では、decode の引数が検証の成否を決めます。
危険なのは、検証フラグを外した「デコードだけ」の実装です。
# 危険:第3引数 false は「署名を検証しない」の意味
payload, _header = JWT.decode(token, nil, false)
user = User.find(payload["user_id"])
これではペイロードの user_id を書き換えたトークンがそのまま通ります。
検証を有効にし、受け入れるアルゴリズムを明示的に固定します。
payload, _header = JWT.decode(
token,
Rails.application.credentials.jwt_secret,
true,
algorithm: "HS256"
)
algorithm を指定すれば、トークン側の alg ヘッダが何であれ、指定外のアルゴリズム(none を含む)は JWT::IncorrectAlgorithm で拒否されます。
RS256 などの公開鍵方式では、検証に渡すのは公開鍵だけにします。
exp クレームはペイロードに含まれていれば既定で検証されますが、そもそも発行時に必ず入れる運用にします。
React(TypeScript)での事例
フロント側の主戦場は、検証ではなくトークンの保管場所です。 ブラウザの JavaScript はトークンを検証できても信用の根拠にはならないため、検証は常にサーバの仕事です。
// 危険:localStorage は同一オリジンのスクリプトから常に読める
localStorage.setItem("access_token", token);
XSS が1箇所でも成立すると、localStorage のトークンはそのまま盗まれます。
Cookie のセッションと違って HttpOnly に相当する保護がなく、盗まれたトークンは有効期限まで攻撃者の手元で使えます。
選択肢は主に二つです。
- HttpOnly Cookie でトークンを保持する:スクリプトから読めなくなる。代わりに Cookie の自動送信が復活するので、CSRF 対策(SameSite とトークン検証)が必要になる
- メモリ上(変数や状態管理)だけで保持する:ページを離れれば消える。リロード時はリフレッシュトークン(HttpOnly Cookie 側)で再取得する
どちらを選んでも、「XSS を1箇所許すと認証が丸ごと漏れる」構造を避けることが目的です。
localStorage 保管は実装が最も簡単ですが、その簡単さはこの構造をそのまま受け入れることと引き換えになっています。
まとめ
JWT認証の脆弱性対策の要点を整理します。
- 根本原因は「署名を正しく検証できていない」か「発行後のトークンを制御できていない」かのどちらか。前者は認証の丸ごと回避、後者は漏洩時の被害の長期化につながる
- 本命の対策は、受け入れる署名アルゴリズムをサーバ側で固定すること。トークンの
algヘッダを信用しなければ、noneの許可とアルゴリズム混同はまとめて塞げる - 検証は実績のあるライブラリに任せ、自作しない。jwt gemなら
decodeの第3引数をtrueにし、algorithm:を明示するだけで指定外のアルゴリズムは例外で弾かれる - アクセストークンは短命にし、リフレッシュトークンと失効リスト(またはセッション)で無効化できる設計にする。ステートレスの利点は「発行後に止められない」という裏返しとセットで来る
- ペイロードは署名されているだけで暗号化されていない。パスワードや個人情報を入れれば、デコードするだけで読まれる
- フロント側の主戦場は保管場所。
localStorageはXSSが1箇所成立した時点で漏れるため、HttpOnly Cookie(+CSRF対策)かメモリ保持を選ぶ
JWTは「ステートレスで手軽」という理由で選ばれますが、その手軽さの一部は検証と失効を省略したところから来ています。既存実装の点検は、decodeの呼び出し箇所でアルゴリズムを固定できているかという1点から始めるのが確実です。
関連して読める記事
トークンを盗まれて本人になりすまされるという構図は、Cookieのセッションを盗まれる場合と同じです。セッションハイジャックの対策(保管場所の保護と失効の設計)はJWTでもそのまま論点になります。また、localStorageのトークンが漏れる引き金はXSSであり、HttpOnly Cookieへ移した場合はCSRF対策が必要になります。3つをセットで読むと、認証情報をどこに置くかの判断がつきます。検証と失効の設計を固めても、トークンを発行させる手前でパスワードだけが認証の壁になっていれば、正規の手順で不正なトークンを取得されます。

セッションハイジャックとは?起きるパターンと対策法
セッションハイジャックの仕組みと起きるパターン、Cookie の HttpOnly / Secure / SameSite 属性と TLS の全面適用を軸にした対策を整理します。Rails のセッションストア設定で属性を明示していない危険例と、secure・httponly・same_site を指定した安全な設定、force_ssl による HTTPS 強制と reset_session によるセッション破棄までを具体的なコードで示します。
2026年7月25日
XSS(クロスサイトスクリプティング)とは?起きるパターンと対策法
XSS(クロスサイトスクリプティング)の仕組みと3つの型、混入しやすいパターン、そして出力時のエスケープを軸にした対策を整理します。React(TypeScript)の dangerouslySetInnerHTML と Rails(ERB)の raw / html_safe を題材に、危険なコードと安全な書き方を具体的なコード例で示します。
2026年7月22日
CSRF(クロスサイトリクエストフォージェリ)とは?起きるパターンと対策法
CSRF(クロスサイトリクエストフォージェリ)の仕組みと起きるパターン、CSRFトークンとSameSite Cookieを軸にした対策を整理します。Railsで検証を外してしまう skip_before_action の罠と、React(TypeScript)からのトークン送信ヘッダの組み込み方を、危険なコードと安全な書き方の具体例で示します。
2026年7月23日
クレデンシャルスタッフィングとは?起きるパターンと対策法
クレデンシャルスタッフィング(リスト型攻撃)の仕組みと起きるパターン、多要素認証とレート制限を軸にした対策を整理します。ブルートフォースとの違い、rack-attack で IP 単位とアカウント単位の両方にしきい値を設ける実装、Devise の lockable や流出パスワード拒否の組み合わせ方を具体的なコードで示します。
2026年7月25日