ログアウトは「画面をログイン前に戻す」だけではない
ログアウト処理では、画面遷移だけでなく、認証状態を無効にする必要があります。
Cookieセッションのログアウトは、サーバー側セッションの削除と、ブラウザ側Cookieの削除をセットで考えます。
どちらか片方だけだと、ログアウトしたように見えても、状態が残ることがあります。
基本のログアウト処理
典型的な流れは次の通りです。
1. ブラウザがログアウトAPIへPOSTする
2. サーバーが現在のsession_idを読む
3. セッションストアから該当セッションを削除する
4. Set-Cookieでブラウザ側Cookieを期限切れにする
5. ログイン画面やトップページへ遷移する
HTTPでは次のようになります。
POST /logout HTTP/1.1
Cookie: session_id=abc123
レスポンス:
HTTP/1.1 302 Found
Set-Cookie: session_id=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax
Cache-Control: no-store
Location: /login
Max-Age=0 は、Cookieを即座に期限切れにする指定です。
サーバー側セッションを削除する理由
Cookieを消すだけでは不十分です。
ブラウザからCookieが消えても、サーバー側にセッションが残っていると、何らかの理由で同じセッションIDが再送されたときに受け付けられる可能性があります。
そのため、ログアウト時はセッションストアから削除します。
DELETE session:abc123
または、監査用に行を残す場合でも、無効状態にします。
{
"sessionIdHash": "8b1a9953",
"userId": "user_001",
"revokedAt": "2026-06-27T10:30:00.000Z",
"reason": "user_logout"
}
セッションIDそのものを監査ログに残すのは避けます。残す場合はハッシュ化や短い識別子にします。
Cookie削除は発行時と条件を合わせる
Cookie削除でよくあるミスは、発行時と違う Domain や Path で削除しようとすることです。
発行:
Set-Cookie: session_id=abc; Domain=example.com; Path=/; HttpOnly; Secure; SameSite=Lax
削除:
Set-Cookie: session_id=; Domain=example.com; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax
削除時に Domain や Path がずれていると、別Cookieを削除しようとしてしまい、元のCookieが残ります。
特に次の構成では注意します。
app.example.comとapi.example.comを使っているDomain=example.comを付けている/adminや/apiなどPathを分けている- 過去の実装で同名Cookieが複数ある
DevToolsのApplication > Cookiesで、同名Cookieが残っていないか確認します。
GETログアウトを避ける
ログアウトは状態変更です。基本は POST にします。
避けたい例:
<a href="/logout">ログアウト</a>
GETログアウトは、リンクのプレビュー、クローラー、画像読み込み、外部サイトからのリンクなどで意図せず発火する可能性があります。
望ましい例:
<form action="/logout" method="post">
<button type="submit">ログアウト</button>
</form>
ログアウトは金銭的被害につながりにくいこともありますが、ユーザー体験やセッション管理上の状態変更です。CSRF対策、SameSite、Origin検証の方針に沿って扱います。
複数タブの挙動
ログアウトで見落としやすいのが複数タブです。
タブA: /dashboard を表示
タブB: /settings を表示
タブBでログアウト
タブAは見た目だけ古い画面のまま
このとき、タブAで次にAPIを呼ぶと 401 になり、ログイン画面へ戻るのが自然です。ただし、タブAの画面がブラウザキャッシュで残ることがあります。
対策:
- 個人ページに
Cache-Control: no-storeを付ける - APIが
401を返したらフロント側でログイン画面へ遷移する - 複数タブへ通知したい場合は
BroadcastChannelやstorage eventを使う - 重要操作前にはサーバー側で必ず再確認する
フロント側の表示を消すだけでは認可になりません。重要なのは、サーバー側でセッションが無効になっていることです。
戻るボタンで画面が見える問題
ログアウト後にブラウザの戻るボタンでマイページが見えることがあります。
この場合、サーバーがログイン済みと判断しているとは限りません。ブラウザが以前のHTMLをキャッシュ表示しているだけのことがあります。
個人ページでは次を検討します。
Cache-Control: no-store
古い互換性が必要な場合は、追加で次を使うこともあります。
Pragma: no-cache
Expires: 0
ただし、最終的な保護はサーバー側認可です。戻るボタンで見える画面からAPIを叩いたときに、無効なセッションなら拒否される必要があります。
全端末ログアウト
「この端末だけログアウト」と「全端末ログアウト」は別です。
| 操作 | やること |
|---|---|
| この端末だけログアウト | 現在のsession_idだけ削除 |
| 全端末ログアウト | そのユーザーの全セッションを削除 |
| パスワード変更後の失効 | 既存セッションを削除、または再認証要求 |
| 管理者による強制ログアウト | 対象ユーザーのセッションを失効 |
セッションストアに userId と sessionId の対応を持たせておくと、全端末ログアウトを実装しやすくなります。
user_sessions:user_001 = [session_a, session_b, session_c]
全端末ログアウト:
DELETE session_a
DELETE session_b
DELETE session_c
DELETE user_sessions:user_001
RDBで管理する場合は、revoked_at や expires_at を持つセッションテーブルにすると、監査や管理画面と相性がよくなります。
JWTログアウトとの違い
Cookieセッションでは、サーバー側セッションを削除すれば即時失効しやすいです。
JWTでは、アクセストークン自体が有効期限を持つため、純粋なステートレス構成では発行済みトークンを即時に止めにくくなります。
| 観点 | Cookieセッション | JWT |
|---|---|---|
| 状態の本体 | サーバー側 | トークン内 |
| ログアウト | セッション削除 | クライアント側削除だけでは不十分 |
| 即時失効 | しやすい | ブラックリスト等が必要 |
| 全端末ログアウト | セッション一覧を削除 | tokenVersion等が必要 |
JWTを使う場合でも、refresh tokenをサーバー側で管理したり、ブラックリストやtokenVersionを使ったりすると、結局何らかの状態管理が入ります。
ログアウトのテスト観点
最低限、次を確認します。
- ログアウト後、保護ページへ直接アクセスするとログイン画面へ戻る
- ログアウト後、APIが
401を返す - Cookieがブラウザから削除される
- セッションストアから該当セッションが消える
- 戻るボタンで表示されても重要APIは通らない
- 複数タブでログアウト後、別タブの操作が拒否される
- 全端末ログアウトで他端末も失効する
- ログアウト処理を複数回呼んでも壊れない
ログアウト処理は冪等にしておくと扱いやすいです。すでにセッションがない状態でログアウトAPIを呼んでも、成功扱いでログイン画面へ戻せばよい場面が多いです。
まとめ
ログアウトは、表示を切り替える処理ではなく、セッションを失効させる処理です。
- サーバー側セッションを削除する
- ブラウザ側Cookieを期限切れにする
- Cookie削除時は発行時のDomain/Pathを合わせる
- GETログアウトは避け、POSTで扱う
- 複数タブや戻るボタンはキャッシュとAPI認可を分けて考える
- 全端末ログアウトにはセッション一覧管理が必要
- JWTでは即時失効に追加設計が必要
「ログアウトしたように見える」ではなく、「古い認証情報ではサーバーが処理を受け付けない」状態にすることが大切です。