世界を動かす技術を、日本語で。

JWTの使用をやめましょう

2026年6月17日原文(gist.github.com)

概要

  • JWT はユーザーログイン維持には不適切
  • セッション管理には クッキーセッション が最適
  • JWTは 短命トークン 用に設計
  • localStorage での認証情報保存は非推奨
  • PASETO など他の安全な手段の活用推奨

JWTをセッション管理に使うべきでない理由

  • JWT は元々、 短期間(5分以内) のトークン用に設計
  • 長期間のユーザーセッション維持には 不向き
  • 安全な「ステートレス認証」は 現実的に不可能
  • 結局、トークン管理のために 状態管理 (データストア)が必要
  • それなら最初から 全データをサーバーに保存 する方が安全
  • JWT で単純なセッショントークンを保存するのは 非効率かつ柔軟性に欠ける
  • セッションCookie の方が 効率的かつ安全
  • JWT仕様 自体が セキュリティ専門家から信頼されていない
  • 過去に 偽トークン生成が可能 だった設計ミスも存在
  • 詳細な問題点はこちらの記事参照

よくある反論・誤解

  • GoogleでもJWTを使っている?

    • Googleは ブラウザ上のユーザーセッション には クッキーセッション を使用
    • JWTは SSO(シングルサインオン) など、サーバー間のセッション移譲用途のみ
    • GoogleのJWT実装は 独自・高セキュリティ で一般利用者とは別物
  • 「ステートレス」の方が優れている?

    • 真に安全なステートレス認証には 膨大なリソース が必要
    • 実際「完全ステートレス」は 実現不可能
    • 詳細はcryto.netの記事参照
  • セッションの設定が分からない?

    • ほとんどの Webサーバーフレームワークセッション機能標準装備
    • Expressなどの Node.jsフレームワーク はモジュール化されているが、 express-session ミドルウェアと storeコネクタ (例:connect-session-knex)で簡単に設定可能

認証情報の保存場所について

  • localStoragesessionStorage での認証情報(特にJWT)保存は 危険
    • 参考記事
    • XSS攻撃 などのリスクが高まるため非推奨

代替手段・推奨技術

  • 短命トークン 用途には PASETO が推奨

    • PASETO はセキュリティ設計が優れており、JWTの問題点を解消
    • ただし、 セッション管理用途には使わないこと
  • セッション管理の仕組み についてはjoepie91のgistが参考

まとめ

  • JWTセッション管理 には使用しない
  • クッキーセッション の利用が 安全かつ簡単
  • PASETOセッションストア など、用途に合った安全な技術選択が重要

Hackerたちの意見

必要な条件:ブラウザベースのユーザーセッション用。JWTはサービス間通信に使うのに良い用途がたくさんあるよ。編集:リンク先のいくつかを読んだ、例えば https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba... 。もしJWTがそんなに恐ろしいほど不安定な標準なら、AWS STSのAssumeRoleWithWebIdentityをハッキングする方法を公開してくれよ、さもなくば公開せずにフォーチュン500のAWSアカウントでクリプトマイナーを立ち上げて利用すればいい。結局成功したら教えてね、だってJWTはそんなに不安定なんでしょ? /sarcasm

JWTは、デフォルトが悪いライブラリのせいで以前は悪かった。数年前はダウングレード攻撃がかなり一般的だった。今ではほとんどの一般的なライブラリがより良いデフォルトを持つようになったので、実際にはかなり安全になったよ。

必要な条件:ブラウザベースのユーザーセッション用。 > JWTはサービス間通信に使うのに良い用途がたくさんある。これはまさに妥当な結論だね。ブラウザでのユーザーセッションのユースケースにはJWTは間違ったツールだと思う。もう少し議論を加えると、JWTの署名や暗号化のことは複雑だよね。一般的なJWTライブラリは今はほとんど整っているけど、昔はそうじゃなかった。多くのライブラリが「none」アルゴリズムを受け入れたり、攻撃者が公開鍵を共有シークレットとして使ってトークンを偽造できるようにしていたこともあったよ。これはリンクされたブログ記事で批判されている複雑さの直接的な結果だね。JWTはユーザーセッションに必要なことができないこともある。無効化するためにはどこかに取り消しリストを保持しないといけない。でも、毎回リクエストで取り消しをチェックする必要があるなら、オペークなセッションIDを使って、毎回それを調べればいいじゃん!確かに短命のトークンを使って常にリフレッシュすることもできるけど、どうして通常のアプリケーションでそれをする必要があるの?それでも、分散システムや機械間通信のユースケースには、署名されたトークンが役立つことがあるのは全く同意するよ。ただ、二つのケースを混同しないでほしいな。 [1] https://nvd.nist.gov/vuln/detail/cve-2022-23540 [2] https://nvd.nist.gov/vuln/detail/CVE-2024-54150(グーグルでランダムに見つけた例で、どのライブラリがこれを悪名高くしたのかは知らない)

君の最初の部分には賛成だけど、編集した部分は論理的な誤りだと思う。何かをハッキングできる必要はないよ。それが安全でないと言えるためにはね。例えば、SAMLの脆弱性をどうやって突くかは知らないけど、XMLパーサーを攻撃対象にしてしまうせいで、ひどい標準だってことは分かってる。私はセキュリティ研究者じゃないから、XMLパーサーの脆弱性を見つける方法は知らないけど、大きな攻撃面を持つのは良くないってことは知ってる。

実際、JWTによって引き起こされた脆弱性の長い系譜があるんだ。

JOSEも、ちゃんと実装されてれば問題が出ることがあるよ。APIの表面がちょっとイマイチなこともあるしね。「正しく保持されていると安全」ってのが「良い」と同じなら、X.509みたいなものにも当てはまるよね。多くのケースにはもっと良い代替手段があって、標準のセッショントークンやAPIキーは、ほとんどの主要なウェブサイトで使われていて、ほとんどのユースケースで完璧に機能するよ。これらの標準が完全に無意味だとは言わないけど、一番の利点はASN.1エンコードみたいに複雑じゃない基本的な標準でデータをやり取りできることだね。ツールがすごく脆弱でバグが多いから。

そうだよね、JWTをチケットのように扱って、ブラウザっぽいものではクッキーに基づくセッションIDに置き換えるのが「ベストプラクティス」って、もう10年以上言われてるんじゃない?それから、クッキーセッションの「ベストプラクティス」を全部やってロックダウンする感じ。

これは「なぜ」の大部分を説明する他のブログ記事へのリンクで、そのブログ記事は「個々のJWTトークンを無効化できない」ということにイライラしているみたい。実装するたびに、一般的なガイドラインはどこかで無効化されたノンスをチェックすることなんだよね。それでそのランダムなブログの2つ目のポイントも解決するし。 >「JWT仕様自体はセキュリティ専門家から信頼されていない。」 これには、ただの一つのブログ記事以上の証拠が必要な気がする。で、そのブログ記事は悪い実装を責めているだけのように見える?どんな標準でもそういう問題はあるよね。全体的に、ランダムなギストリンクをクリックして何を期待していたのか分からない。

そうだね…初期の実装では、ヘッダーに任意の権限を設定できて、それを信頼していたから、もちろん最初から間違ってたよ…信頼できる「既知の」権限だけを許可すれば、文脈上の懸念はほとんど無くなる。これを超えて、ブラウザで短命のJWTを作るのは簡単だし、エージェントが自己更新できるよ。Azure Entraや他のいくつかのプロバイダーを使うと、実際にこういう風に機能する…JWTを比較的短命(5〜15分)に保って、jtiの取り消しもチェックできる。JWTはアプリケーション/APIシステムからアクセス権限を分離/再利用するのに非常に便利だよ。攻撃面をシフトさせて、信頼できる方法で行える。私たちはPPKを世界中のSSHなど、いろんなことに使ってる。共有シークレットは使わないし、長寿命のトークンも使わないけど、短命でPPK署名されたトークンは信頼できる/既知のソースからなら一般的には大丈夫だよ。それにしても、実際に問題になるのはAPIキーだよね。実装しなきゃいけなかったんだけど…私にとってAPIキーはBearerトークンとしても表示されるけど、短い「sak.」プレフィックスの後にアイデンティティ部分(base64url UUIDバイト)が続いて、その後にシークレットがbase64urlバイトで続く…データベースにはUUIDとシークレットからのパスフレーズレベルのソルト+ハッシュがあるから、生成されたAPIキーは秘密として扱われ、データベースに対して一方向だから、データベースの侵害が認証を侵害することはない。とはいえ、APIキーの漏洩は、きちんと実装されたJWTソリューションの問題よりもずっと起こりやすい。

「個々のJWTトークンを無効化できない。」 実装するたびに、一般的なガイドラインはどこかで無効化されたノンスをチェックすることなんだよね。それでそのランダムなブログの2つ目のポイントも解決するし。100%同意するよ。これは常識だと思うし、みんながこれをやらないことに驚くことが多い。

JWTは不安定だよ…信頼できるRSA/PPKベースの署名方法を使っても?共有シークレットじゃなくて。JWTは長寿命すぎる…JWTの有効期限を制限して、認証機関に対してリフレッシュモデルを持つことを止めるものは何もないよ…クッキーを使ったセッションでも、どこかに保存しているし…5〜15分有効なJWTを持つことができるよ。15分は多くの認証システム、特にEntraのキャッシュタイミングと大体同じだし…リフレッシュシステムを使った5分のトークンもブラウザから問題なく使える。最後に、アイデンティティ/認証はアプリケーション/APIサービスから分けておきたいな…それによってコンテキストが外部化されて、リクエストごとのJWTの方が、時々失敗するかもしれない共有キャッシュ/状態システムより扱いやすいから。

JWTは30秒後や1秒後に無効にすることができるよ。JWTを作成する時はaud(オーディエンス)を設定すべきだね。そうしないと、署名は暗号的には正しい状態になる。短い有効期限で、毎回すべてのJWTを検証するのが大事だよ。ちなみに、OIDCトークンは全部JWTだからね。

Hacker Newsで議論の続きを見る