はじめに: 「ログイン状態」の保ち方
HTTP はステートレスなプロトコルで、リクエストごとに「あなたは誰なのか」をサーバ側に思い出させる仕組みが必要です。これを実現する代表的な方式が2つ:
- セッション認証(伝統的、サーバ状態あり)
- JWT 認証(モダン、ステートレス)
この記事では「結局どっちがいいの?」という問いに、シーン別の答えを示します。結論を先に書くと:「Webサービスのログインなら原則セッション、API/SPA/モバイルとの組み合わせで JWT、組織横断SSOなら OIDC」 ──ですが、なぜそうなるのかを順を追って整理します。
方式1: セッション認証
1990年代から続く伝統的な方式。仕組みは単純:
- ユーザーがログイン → サーバはランダムなセッションIDを発行
- サーバ側 DB / Redis に
session_id → user_idのマッピングを保存 - クライアントには Set-Cookie でセッション ID を渡す
- 以降のリクエストでは、ブラウザが Cookie を自動付与してサーバに送信
- サーバは Cookie のセッション ID で DB を引いて、ユーザーを特定
# ログインレスポンス
HTTP/1.1 200 OK
Set-Cookie: sid=a1b2c3d4e5f6; HttpOnly; Secure; SameSite=Lax
# 以降のリクエスト
GET /api/me HTTP/1.1
Cookie: sid=a1b2c3d4e5f6セッション ID 自体は意味のないランダム文字列で、サーバ側のセッションストアが「真実の保管場所」です。
セッション認証の利点
- サーバ側で即時に無効化可能: ログアウト時、不正検知時、セッションストアからレコードを消すだけ
- 権限変更が即時反映: 「この人を管理者から外す」が次のリクエストで反映
- クライアント側にユーザー情報を持たない: 漏洩時の被害が限定的
- 枯れていて実装が安定: フレームワーク標準サポート(Rails / Django / Laravel / Express-session)
セッション認証の欠点
- サーバ側に状態を持つ: スケールアウトする時、複数サーバ間でセッションストアを共有する必要(Redis / DB)
- クロスドメインで使いづらい: 別ドメインの API を叩く時、Cookie の SameSite 制約や CORS で詰まる
- モバイルアプリで Cookie が扱いづらい: WebView 経由でないと自然にいかない
方式2: JWT 認証
2010年代に SPA / モバイルアプリの普及とともに広まった方式。JWT(JSON Web Token)はユーザー情報そのものをトークンに署名付きで詰め込む仕組み。
例えばログインすると、サーバはこんなトークンを返します:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE3MzAwMDAwMDB9.signature
# Base64 デコードすると…
{"alg":"HS256"}.{"sub":"123","exp":1730000000}.{署名}Payload に sub(ユーザーID)や exp(有効期限)を入れ、サーバの秘密鍵で署名します。サーバは受け取ったトークンの署名を検証するだけでユーザーを認証できる。サーバ側にセッションストア不要がポイント。
# 以降のリクエスト
GET /api/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWI...JWT 認証の利点
- ステートレス: サーバ側にセッションストア不要、水平スケールが楽
- マイクロサービス間で共有しやすい: トークン1個で複数サービスが認証可能
- クロスドメインで使いやすい:
Authorizationヘッダ経由で Cookie の制約を受けない - モバイル/SPAで自然: ブラウザ依存の Cookie ではなく、アプリのストレージで管理可能
- ユーザー情報を含められる: 各リクエストで DB を引かなくても
sub,role等が分かる
JWT 認証の欠点
- 即時失効が困難: 一度発行したトークンは
expまで有効。不正検知して即ログアウトさせたい時にブラックリストが必要 → 結局サーバ状態を持つ - 権限変更がリアルタイム反映されない: 「管理者から降格」を実行しても、次のトークン更新まで反映されない
- ペイロードが大きい: Cookie/ヘッダのサイズが膨らむ(数百バイト〜KB単位)
- 署名検証の落とし穴が多い: alg=none、アルゴリズム混同、弱い秘密鍵等。詳細は JWT のよくあるセキュリティ問題 参照
保管場所: Cookie か localStorage か
JWT を採用すると次に必ず議論になるのが「ブラウザでどう保管するか」。選択肢は3つあり、それぞれ XSS / CSRF への耐性が違います。
| 保管場所 | XSS耐性 | CSRF耐性 | 送信 |
|---|---|---|---|
| localStorage | ❌ JS から読めるので窃取可 | ✅ 自動送信されない | JS で Authorization ヘッダ付与 |
| HttpOnly Cookie | ✅ JS から読めない | ❌ 自動送信されるので CSRF 対策必要 | ブラウザが自動付与 |
| メモリ(変数) | △ JS スコープ次第 | ✅ 自動送信されない | リロードで消える |
現代の推奨は「アクセストークンはメモリ、リフレッシュトークンは HttpOnly Cookie」のハイブリッドパターンが多いです。XSS の影響範囲を「短命なメモリトークンの窃取」に限定でき、長命のリフレッシュトークンは JS から触れない位置に置く。
⚠ 「localStorage に JWT を入れる」はセキュリティ的に最弱。XSS 1個で全ユーザーのトークンが盗まれます。
リフレッシュトークン: 寿命の使い分け
JWT を実運用すると、すぐに「即時失効できない問題」と「短命にすると再ログインが多すぎる問題」のジレンマに直面します。解決策が2種類のトークンを使い分けるパターン:
- アクセストークン: 短命(5〜15分)、API 呼び出しに使用、漏洩しても影響限定
- リフレッシュトークン: 長命(数日〜数週間)、アクセストークン更新専用、サーバ側で失効管理
フロー:
- ログイン → 両トークンを発行
- API 呼び出しはアクセストークンで(普通の JWT 検証のみ、サーバ状態不要)
- アクセストークンが期限切れになったら、リフレッシュトークンで新しいアクセストークンを取得
- リフレッシュ時にサーバ側で「このリフレッシュトークンが失効していないか」を DB で確認
この設計だと、「99% のリクエストはステートレス、ログイン延長の瞬間だけサーバ状態を読む」という良いとこ取りができます。
結局どちらを選ぶ?
シナリオA: 一般的な Web サービス(SSR ベース)
Rails / Django / Laravel 等のサーバーサイドレンダリングが主体で、フロントエンドが同じドメインで動く構成。
→ セッション認証。フレームワーク標準、安全、シンプル。JWT を導入するメリットは薄い。
シナリオB: SPA + 別ドメインの API
React/Vue の SPA がフロント、API が別サーバ(別ドメイン)。
→ JWT または同ドメインに寄せたセッション。
- API を
api.example.com、フロントをapp.example.comのようにサブドメイン構成にすれば Cookie でセッション認証も可能 - 完全別ドメイン or マイクロサービス構成なら JWT の方が素直
シナリオC: モバイルアプリ + API
ネイティブアプリと API サーバ。
→ JWT。Cookie ベースのセッションは扱いづらい。アクセス + リフレッシュトークンで運用。
シナリオD: マイクロサービス / 複数システム
サービス A → サービス B → サービス C と認証を引き継ぐ必要がある構成。
→ JWT or OIDC。各サービスが独立に署名検証できる JWT が向く。組織横断 SSO なら OpenID Connect (OIDC)(中身は JWT ベース)。
シナリオE: 第三者サービス連携(OAuth)
「Google でログイン」のような第三者連携。
→ OAuth 2.0 / OIDC。自前で JWT を発行する話とは別軸で、第三者の発行する ID トークン(JWT)を受け取る形。
「JWT がモダンだから JWT」は間違い
2015〜2018年頃の SPA ブームで「セッションは古い、これからは JWT」という空気がありましたが、2026年現在の評価はかなり揺り戻しが起きています:
- JWT を「使うべきでないところ」で使うとハマるポイントが多すぎる
- 「JWT in localStorage」の事例ほぼ全てがセキュリティ的にアウト
- セッション認証 + Cookie の組み合わせが、2026 年の SameSite 既定値・HttpOnly・Secure と相まって極めて堅牢になった
Hacker News や Reddit の議論でも「Stop using JWT for sessions」の論調が定着しています。「ステートレスである必要が本当にあるのか」を最初に問うのが大事。
セキュリティチェックリスト
どちらを選んでも共通でやること:
- HTTPS 必須: 平文 HTTP で Cookie/JWT が飛んだら盗聴で終わり(HTTPS と TLS の仕組み)
- HttpOnly + Secure + SameSite=Lax: Cookie の3点セット(CSRF)
- 適切な失効/期限: アクセストークンは短く、リフレッシュトークンは管理可能に
- パスワード保存は別問題: 認証方式を選ぶ前に 適切なハッシュ関数 で保存
- 多要素認証で底上げ: MFA を併用
- JWT を使うなら署名検証の罠を理解: JWT のよくあるセキュリティ問題
おわりに
セッション vs JWT は「モダン or 古い」ではなく「使う場所が違う」という関係です。新規プロジェクトでは「ステートレスな必要性が本当にあるか」を最初に考え、必要なければセッションが安全で楽。SPA + API / モバイル / マイクロサービスのような分散構成では JWT が活きます。
本サイトの JWT Decoder で実際の JWT トークンを分解し、署名検証まで実行できます。HTTP Cookie Parser で Set-Cookie の SameSite/HttpOnly 設定をチェックすれば、セッション側の運用も整理できます。