2025年9月29日·1分で読めます

ウェブアプリのセッション管理:クッキー vs JWT vs リフレッシュ

ウェブアプリのセッション管理を比較:クッキーベースのセッション、JWT、リフレッシュトークンを脅威モデルと現実的なログアウト要件で考察します。

ウェブアプリのセッション管理:クッキー vs JWT vs リフレッシュ

セッション管理が本当にやっていること

セッションとは、誰かがログインした後にアプリが答える一つの質問です: 「あなたは今誰ですか?」 信頼できる答えが得られれば、アプリはそのユーザーが何を見られるか、何を変更できるか、どの操作をブロックするかを判断できます。

「ログイン状態を維持する」こともセキュリティ上の選択です。どれくらいの期間ユーザーの識別情報を有効にするか、識別の証明がどこに置かれるか、その証明がコピーされたらどうするかを決めています。

ほとんどのウェブアプリ構成は次の三つの要素に依存します:

  • Cookie-based server sessions:ブラウザはクッキーに値を保持し、サーバーが各リクエストでセッションを参照します。
  • JWT access tokens:クライアントが署名済みトークンを送り、サーバーはデータベース参照なしで検証できます。
  • Refresh tokens:より長期間有効な資格情報で、新しい短命のアクセストークンを取得するために使います。

これらは競合する「スタイル」ではなく、同じトレードオフを扱う別の方法です:速度と制御、単純さと柔軟性、そして「今すぐ無効化できるか?」と「自然に期限切れになるか?」の違いです。

評価に役立つ問いはこうです:攻撃者がアプリの証明(クッキーやトークン)を盗んだ場合、何ができてどれくらい続くか? 強いサーバー側の制御が必要な場合(強制ログアウトや即時ロックアウトなど)、クッキーセッションは有利です。JWTはマルチサービスでのステートレスな検証に向きますが、即時取り消しが必要だと厄介になります。

どれか一つがすべてに勝つわけではありません。正しいアプローチはあなたの脅威モデル、ログアウト要件の厳しさ、チームが現実的に維持できる複雑さによって決まります。

正しい答えを変える脅威モデル

良いセッション設計は「最良の」トークンタイプよりも、どの攻撃に耐える必要があるかに依存します。

攻撃者がブラウザストレージ(localStorageなど)からデータを盗む場合、JWTアクセストークンはページのJavaScriptから読み取れるため簡単に奪われます。盗まれたクッキーは状況が異なります:HttpOnly に設定されていれば通常のページコードは読めないため、単純な「トークン横取り」攻撃は難しくなります。しかし、デバイス自体(紛失したラップトップ、マルウェア、共有コンピュータ)を攻撃者が持っている場合、ブラウザプロファイルからクッキーをコピーされる可能性は残ります。

XSS(攻撃者コードがあなたのページ上で実行される)は全てを変えます。XSSがあれば、攻撃者は秘密を盗まなくても被害者の既存ログインセッションを使って操作できます。HttpOnlyクッキーはセッション秘密の読み取りを防ぎますが、ページからリクエストを送ること自体は止められません。

CSRF(別サイトからの望ましくない操作の引き起こし)は主にクッキーベースのセッションを脅かします。ブラウザが自動的にクッキーを付与するためです。クッキーに頼る場合は明確なCSRF防御が必要です:意図したSameSite設定、アンチCSRFトークン、状態変更リクエストの慎重な扱いなど。Authorizationヘッダで送られるJWTは古典的なCSRFには晒されにくいですが、JavaScriptで読み取れる場所に保存しているとXSSには脆弱です。

リプレイ攻撃(盗まれた資格情報の再利用)は、サーバー側セッションが得意な分野です:セッションIDを即座に無効化できます。短命のJWTはリプレイ時間を短くしますが、トークンが有効である間はリプレイを防げません。

共有デバイスや紛失した電話は「サインアウト」を現実的な脅威モデルにします。決定は通常次のような問いに落ち着きます:ユーザーは他のデバイスから強制ログアウトできるか、どれくらい速くそれが有効になるか、リフレッシュトークンが盗まれたらどうするか、「ログインを記憶する(remember me)」を許すか。多くのチームはスタッフのアクセスに対して顧客より厳しい基準を設け、タイムアウトや取り消し期待を変えます。

クッキーセッション:仕組みと守るもの

クッキーベースのセッションは古典的な構成です。サインイン後、サーバーはセッションレコード(通常はIDとユーザーID、作成時間、有効期限などのフィールド)を作ります。ブラウザはセッションIDだけをクッキーに保存し、各リクエストでそのクッキーを返します。サーバーはセッションを見てユーザーを判断します。

大きなセキュリティ上の利点は制御力です。セッションはその都度サーバーで検証されます。誰かをキックアウトする必要があるなら、サーバー側のセッションレコードを削除または無効化すれば、それで直ちに効力を失います。ユーザーがクッキーを持ち続けていてもサーバーが拒否します。

多くの保護はクッキー設定から来ます:

  • HttpOnly:JavaScriptからクッキーを読み取れないようにします。
  • Secure:HTTPSでのみクッキーを送信します。
  • SameSite:クロスサイトのリクエストでクッキーが送信されるタイミングを制限します。

セッション状態の保管場所はスケーリングに影響します。アプリのメモリに保持するのは簡単ですが、複数サーバーを走らせるときや頻繁に再起動する環境では破綻します。耐久性を求めるならデータベースが適します。Redisは高速なルックアップと多くのアクティブセッションを扱う場合に一般的です。重要な点は同じ:サーバーは各リクエストでセッションを見つけて検証できなければならない、ということです。

クッキーセッションは、スタッフ用ダッシュボードや管理者が役割変更後に強制ログアウトできるような場面で強力に適合します。従業員が退職した場合、サーバー側のセッションを無効にすればトークンの有効期限を待たずにアクセスを止められます。

JWTアクセストークン:強みと注意点

JWT(JSON Web Token)はユーザーに関するいくつかのクレーム(ユーザーID、ロール、テナント)と有効期限情報を持つ署名付き文字列です。APIは署名と有効期限をローカルで検証し、データベースを呼ぶことなくリクエストを認可します。

このためJWTはAPIファーストのプロダクト、モバイルアプリ、複数のサービスが同じ識別を検証する必要があるシステムで人気です。複数のバックエンドインスタンスがあっても、それぞれが同じトークンを検証して同じ答えを得られます。

強み

JWTアクセストークンは検証が速く、API呼び出しで渡しやすいです。フロントエンドが多数のエンドポイントを呼ぶなら、短命のアクセストークンによりフローがシンプルになります:署名を検証し、ユーザーIDを読み取り、続行します。

例:顧客ポータルが別サービスの「List invoices」や「Update profile」を呼ぶケース。JWTは顧客IDや customer のようなロールを運べるので、各サービスはセッションを毎回参照せずに認可できます。

注意点

最大のトレードオフは取り消しです。トークンが1時間有効なら、通常その1時間はどこでも有効で、ユーザーが「ログアウト」を押したり管理者がアカウントを無効にしても、追加のサーバー側チェックを入れない限り効力は続きます。

JWTは通常の方法で漏洩します。一般的な失敗点にはlocalStorage(XSSで読まれる)、ブラウザメモリ(悪意ある拡張機能)、ログやエラーレポート、プロキシや解析ツールがヘッダをキャプチャすること、サポートチャットやスクリーンショットにコピーされたトークンなどがあります。

このためJWTアクセストークンは「永続ログイン」向けではなく、短命のアクセスに向いています。中に機微な個人情報を入れず、有効期限を短くし、盗まれたらその有効期限まで使えると仮定する設計にしてください。

リフレッシュトークン:JWT構成を実用的にするもの

安全なカスタマーポータルを出荷する
1つのプロジェクトから本番準備が整ったウェブアプリとバックエンドを生成します。
アプリを作成

JWTアクセストークンは短命であることが望ましいです。それは安全ですが、現実的な問題を生みます:ユーザーが数分ごとに再ログインするべきではありません。リフレッシュトークンは、古いアクセストークンが切れたときにアプリが静かに新しいアクセストークンを取得できるようにします。

リフレッシュトークンをどこに保存するかはアクセストークン以上に重要です。ブラウザベースのウェブアプリでは、JavaScriptから読めないHttpOnlyかつSecureなクッキーに置くのが最も安全な既定です。localStorageは実装が簡単ですが、XSSバグがあると盗まれやすくなります。脅威モデルがXSSを含むなら、長期間有効な秘密をJavaScriptで読める場所に置くのは避けてください。

ローテーションこそがリフレッシュトークンを実運用で実用的にします。同じリフレッシュトークンを何週間も使う代わりに、使用するたびに入れ替えます:クライアントがリフレッシュトークンAを提示すると、サーバーは新しいアクセストークンとリフレッシュトークンBを発行し、Aは無効になります。

シンプルなローテーション運用は通常次のルールに従います:

  • アクセストークンは短く保つ(分単位、時間ではなく)。
  • リフレッシュトークンはサーバー側でステータスと最終使用時間を保持する。
  • リフレッシュ時にローテーションし、前のトークンを無効にする。
  • 可能ならリフレッシュトークンをデバイスやブラウザに紐づける。
  • 不正利用を調査できるようにリフレッシュイベントをログに残す。

再利用検知が重要な警報です。リフレッシュトークンAが既に交換されているのに再び使われたら、それはコピーされたと仮定します。一般的な対応はセッション全体(場合によってはそのユーザーの全セッション)を取り消して再ログインを要求することです。どちらが本物のコピーかを判断できないためです。

ログアウトに関しては、サーバーで強制できる仕組みが必要です。通常はリフレッシュトークンの無効化を示すセッションテーブル(または取り消しリスト)を持つことを意味します。アクセストークンは有効期限まで動くかもしれませんが、有効期限を短くすることでそのウィンドウを小さくできます。

ログアウト要件と実際に実現できること

ログアウトは定義すると単純ではなくなります。通常は二つの要求があります:「このデバイスをログアウトする」(あるブラウザや電話)と「全てのデバイスをログアウトする」(全アクティブセッション)。

またタイミングの問題もあります。「即時ログアウト」はその資格情報を今すぐ受け付けなくすることを意味します。「期限切れによるログアウト」は現在のセッションやトークンの自然な期限切れで受け付けなくすることを意味します。

クッキーベースのセッションでは、即時ログアウトは簡単です。サーバーがセッションを所有しているからです。クライアント側でクッキーを削除し、サーバー側のセッションレコードを無効にすれば完了します。以前にクッキーの値をコピーされていたとしても、サーバーの拒否がログアウトを実際に強制します。

JWTのみの認証(ステートレスなアクセストークンでサーバー参照がない)は、真の意味での即時ログアウトを保証できません。盗まれたJWTは有効期限まで有効だからです。拒否リスト(denylist)を追加できますが、その場合は状態を保持してチェックすることになり、元の単純さは失われます。

実際的なパターンはアクセストークンを短命にし、ログアウトはリフレッシュトークンで管理することです。アクセストークンは数分の猶予を持って動き続けますが、セッションを維持するのはリフレッシュトークンです。ラップトップが盗まれた場合、リフレッシュトークンの一族を取り消せば将来のアクセスを素早く切れます。

ユーザーに実際に約束できること:

  • このデバイスをログアウト:そのセッションまたはリフレッシュトークンを取り消し、ローカルのクッキーやストレージを削除する。
  • 全てをログアウト:そのアカウントの全セッションや全リフレッシュトークンのファミリーを取り消す。
  • 「即時」効果:サーバーセッションでは保証される。アクセストークンでは有効期限まではベストエフォート。
  • 強制ログアウトイベント:パスワード変更、アカウント無効化、ロール降格など。

パスワード変更やアカウント無効化では「ユーザーがログアウトするのを待つ」べきではありません。アカウント全体のセッションバージョン(または「この時刻以降有効」タイムスタンプ)を保存しておき、各リフレッシュ(場合によっては各リクエスト)で比較します。変わっていれば拒否して再サインインを要求します。

ステップバイステップ:アプリに適したセッション方式の選び方

適切な認証の構築
ウェブとモバイルで制御できるログインフローを作成します。
AppMasterを試す

セッション設計を単純に保ちたいなら、仕組みを選ぶ前にルールを決めてください。多くの問題は、チームがリスクやログアウト要件に合わせずにJWTやクッキーを人気だからと選んでしまうところから始まります。

まずユーザーがどこでサインインするかを全部書き出してください。ブラウザアプリはネイティブモバイルアプリ、内部管理ツール、パートナー統合と異なる振る舞いをします。それぞれ安全に保存できる場所、ログインの更新方法、「ログアウト」が意味することを変えます。

多くのチームで実用的な順序は次の通りです:

  1. クライアントを列挙する:Web、iOS/Android、内部ツール、サードパーティアクセス。
  2. デフォルトの脅威モデルを決める:XSS、CSRF、デバイス窃盗など。
  3. ログアウトが何を保証すべきかを決める:このデバイス、全デバイス、管理者による強制ログアウト。
  4. ベースラインパターンを選ぶ:クッキーベースのセッション(サーバーで記憶)か、アクセストークン+リフレッシュトークンか。
  5. タイムアウトと応答ルールを設定する:アイドル別と絶対有効期限、疑わしい再利用を見たときの対応。

その後、システムが実際に約束する内容を文書化します。例:「ウェブセッションはアイドルで30分、最大で7日で失効。管理者は60秒以内に強制ログアウトできる。紛失した電話は遠隔で無効化できる。」 こうした文言は、使うライブラリより重要です。

最後に、あなたのパターンに合った監視を追加してください。トークン構成では、強力なシグナルはリフレッシュトークンの再利用です(同じトークンが二度使われる)。それを盗難と見なし、セッションファミリを取り消してユーザーへ通知するようにしましょう。

アカウント乗っ取りにつながる一般的なミス

変更を負債なく反映する
要件が変わっても負債を増やさずに認証ルールを素早く更新します。
コードを再生成

多くのアカウント乗っ取りは「賢いハック」ではなく、予測可能なセッションミスによる単純な勝利です。良いセッション処理は攻撃者に資格情報を盗んだり再利用したりする簡単な方法を与えないことに尽きます。

よくある落とし穴の一つはアクセストークンをlocalStorageに置き、XSSが発生しないことを期待することです。ページ上でスクリプトが実行されれば(不当な依存、埋め込みウィジェット、保存されたコメントなど)、localStorageを読んでトークンを送信できます。HttpOnly フラグを付けたクッキーはこのリスクを減らします。

もう一つの落とし穴はリフレッシュトークンを避けるためにJWTを長期間有効にすることです。7日間有効なアクセストークンは、漏洩すれば7日間の再利用ウィンドウを与えます。短いアクセストークンと適切に管理されたリフレッシュトークンの組合せは、特にリフレッシュを切れる場合に悪用を難しくします。

クッキーにはCSRF対策を忘れるという自分の足を撃つ落とし穴もあります。クッキー認証で状態変更リクエストをCSRF防御なしに受け付けると、悪意あるサイトがログイン済みブラウザに有効なリクエストを送らせることができます。

インシデントレビューでよく見つかる他のミス:

  • リフレッシュトークンをまったくローテーションしていない、またはローテーションしても再利用を検出していない。
  • 複数のログイン方法(クッキーセッションとベアラートークン)をサポートしていて、サーバー側で「どちらが優先されるか」のルールが不明確。
  • トークンがログに残る(ブラウザコンソール、解析イベント、サーバーリクエストログ)、それによりコピーされ保持される。

具体例:サポート担当者が「デバッグログ」をチケットに貼り付け、そのログに Authorization ヘッダが含まれていたとします。チケットにアクセスできる誰でもそのトークンをリプレイして担当者として操作できます。トークンはパスワードのように扱ってください:印刷しない、保存しない、短命にする。

出荷前に行う簡単なチェック

ほとんどのセッションバグは難しい暗号の問題ではありません。一つのフラグの付け忘れ、長く生きるトークン、再認証を要求すべきエンドポイントの見落としです。

リリース前に、盗まれたクッキーやトークンで攻撃者が何ができるかに焦点を当てた短いパスを実行してください。これは全体を書き換えずにセキュリティを向上させる最速の方法の一つです。

リリース前チェックリスト

ステージングでこれらを確認し、本番でも再度チェックしてください:

  • アクセストークンを短く保つ(分単位)こと、APIが期限切れ後に実際に拒否することを確認する。
  • リフレッシュトークンはパスワードのように扱う:できればJavaScriptから読めない場所に保管し、リフレッシュ専用のエンドポイントにだけ送信し、使用ごとにローテーションする。
  • クッキーで認証するならフラグを確認する:HttpOnly をオン、Secure をオン、SameSite を意図的に設定。クッキーのスコープ(ドメインとパス)が必要以上に広くないことも確認する。
  • クッキーで認証する場合はCSRF防御を追加し、状態変更エンドポイントがCSRF信号なしに失敗することを確認する。
  • 取り消しを現実的にする:パスワードリセットやアカウント無効化の後、既存セッションが速やかに機能しなくなることを確認する(サーバー側セッション削除、リフレッシュトークンの無効化、または「セッションバージョン」チェック)。

その後、ログアウトの約束をテストしてください。「ログアウト」は多くの場合「ローカルセッションを削除する」ことを意味しますが、ユーザーはそれ以上を期待します。

実用的なテスト:ラップトップと携帯でログインし、パスワードを変更します。ラップトップは次のリクエストで強制ログアウトされるべきで、数時間後であってはいけません。もし「全てをログアウト」とデバイス一覧を提供するなら、各デバイスが取り消し可能な別々のセッションまたはリフレッシュトークンレコードに対応していることを確認してください。

例:スタッフアカウントと強制ログアウトがあるカスタマーポータル

モバイルサインインも追加
同じ認証ルールを共有するネイティブiOS/Androidアプリを作ります。
モバイルを構築

小さな事業があり、ウェブのカスタマーポータル(顧客は請求書確認、チケット作成)、現場スタッフ用のモバイルアプリ(作業、ノート、写真)を持っていると想像してください。スタッフは電波の届かない地下で作業することがあり、アプリはある程度オフラインでも動作する必要があります。管理者は大きな赤いボタンを欲しがります:タブレットが紛失したり契約者が退職したりしたら強制ログアウトしたいと。

そこに三つの共通脅威を追加します:バンの共有タブレット(誰かがサインアウトし忘れる)、フィッシング(スタッフが偽ページに認証情報を入力する)、ポータルに時折発生するXSSバグ(スクリプトがブラウザで動きトークンを盗もうとする)。

実用的な構成は短命のアクセストークンとローテーションするリフレッシュトークン、そしてサーバー側の取り消し記録の組合せです。これにより高速なAPI呼び出しとオフラインの許容、かつ管理者がセッションを切れることを両立できます。

具体例:

  • アクセストークン寿命:5〜15分。
  • リフレッシュトークンのローテーション:リフレッシュのたびに新しいリフレッシュトークンを返し、古いものを無効化する。
  • リフレッシュトークンの安全な保管:WebではリフレッシュトークンをHttpOnlyかつSecureなクッキーに入れ、モバイルではOSのセキュアストレージに入れる。
  • サーバー側でリフレッシュトークンを追跡:トークンレコード(ユーザー、デバイス、発行時刻、最終使用時刻、無効フラグ)を保存する。ローテーション後のトークンが再利用されたら盗難と見なし、チェーン全体を取り消す。

強制ログアウトは実効化できます:管理者がそのデバイス(またはユーザーの全デバイス)のリフレッシュトークンレコードを取り消します。盗まれたデバイスは現在のアクセストークンをアクセストークンの有効期限までは使い続けるかもしれませんが、新しいトークンは取得できません。したがって完全にアクセスを切る最大時間はアクセストークンの寿命です。

紛失デバイスについては、次のような平易なルールを定義してください:「10分以内にアプリは同期を停止し、再度サインインが必要になります。」オフライン作業はデバイス上で続けられますが、次にオンラインで同期しようとするとサインインが必要になります。

次のステップ:実装、テスト、そして保守可能に保つ

「ログアウト」が何を意味するかをプロダクトの平易な言葉で書き下してください。例:「このデバイスのログアウトは端末上のアクセスを削除する」「全てのデバイスをログアウトすると1分以内に全端末をキックアウトする」「パスワード変更は他のセッションをログアウトさせる」。これらの約束がサーバー側セッション、取り消しリスト、短命トークンのどれを必要とするかを決めます。

約束を小さなテスト計画に落とし込みましょう。トークンやセッションのバグはハッピーパスのデモでは問題なく見えることが多く、実際の運用(スリープモード、断続的ネットワーク、複数デバイス)で失敗します。

実用的なテストチェックリスト

次のようなケースをカバーするテストを実行してください:

  • 有効期限:アクセストークンやセッションが期限切れ時にアクセスを止める。ブラウザが開いたままでも有効期限後に止まること。
  • 取り消し:"全てをログアウト" の後、古い資格情報が次のリクエストで失敗すること。
  • ローテーション:リフレッシュトークンのローテーションが新しいトークンを発行し、古いトークンを無効にすること。
  • 再利用検知:古いリフレッシュトークンをリプレイするとロックダウン応答が起きること。
  • マルチデバイス:「このデバイスのみ」と「全デバイス」のルールが強制され、UIがそれに一致すること。

テスト後、チームで簡単な攻撃リハーサルを行ってください。三つのストーリーを選び、エンドツーエンドで検証します:トークンを読み取れるXSSバグ、クッキーセッションに対するCSRF試行、アクティブセッションを持つ紛失電話。設計が約束と一致しているかを確認します。

迅速に進める必要があるなら、カスタムの接着コードを減らしてください。AppMaster(appmaster.io)は、生成された本番対応のバックエンドとウェブ・ネイティブのアプリを提供するオプションの一つで、expiry、ローテーション、強制ログアウトのルールをクライアント間で一貫させるのに役立ちます。

ローンチ後のフォローアップレビューをスケジュールしてください。実際のサポートチケットやインシデントを使ってタイムアウト、セッション制限、"全てをログアウト" の挙動を調整し、修正が静かに退行しないように同じチェックリストを再実行します。

始めやすい
何かを作成する 素晴らしい

無料プランで AppMaster を試してみてください。
準備が整ったら、適切なサブスクリプションを選択できます。

始める