Cookie・セッション・JWT(Web認証)
HTTP は毎回「初対面」。だからログイン状態を覚えさせる仕組みが要る。Cookie で情報を運び、サーバセッションか JWT で「誰か」を保持する。
- 1.HTTP はステートレス(前回を覚えない)。だから「ログイン済み」を伝える仕掛けが必要で、その運び役が Cookie。
- 2.状態の持ち方は2系統。サーバ側に正体を置く「セッション(セッションID)」と、署名済みの情報を本人に持たせる「JWT(トークン)」。
- 3.保存場所がセキュリティを左右する。HttpOnly Cookie は XSS に強いが CSRF 対策が要る。localStorage は CSRF に強いが XSS で盗まれる。
なぜ状態保持が要るのか
HTTP は1回のリクエスト/レスポンスで完結し、前回を覚えていません。ログインフォームに ID とパスワードを送って認証が通っても、次の GET /mypage は別の通信なので、サーバは何も知らない状態からやり直します。
毎回パスワードを送らせるのは論外(盗聴・漏洩リスク)。そこで「最初に1回だけ本人確認 → 以降は短い合言葉で済ます」という流れにします。この合言葉をブラウザとサーバの間で持ち回る仕組みが、これから見る Cookie とセッション/JWT です。
Cookie:印を運ぶ箱
Cookie は、サーバがブラウザに「これ持っといて」と渡す小さなテキストです。仕組みはシンプルで、ヘッダのやり取りだけで成立します。
# サーバ → ブラウザ(ログイン成功時のレスポンス)
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600
# 以降、ブラウザ → サーバ(同じサイトへのリクエストに自動で付く)
GET /mypage HTTP/1.1
Cookie: session_id=abc123
ポイントは、一度受け取った Cookie をブラウザが同じサイト宛のリクエストに自動で付け続けること。開発者が JS で何もしなくても付与されます(後述の CSRF はこの「自動で付く」性質が原因)。よく使う属性を押さえておきましょう。
| 属性 | 役割 | ひとこと |
|---|---|---|
| HttpOnly | JS(document.cookie)から読めなくする | XSS で盗まれにくくする最重要属性 |
| Secure | HTTPS のときだけ送信する | 平文での漏洩を防ぐ |
| SameSite | 別サイト発のリクエストに付けない | Lax/Strict で CSRF を緩和 |
| Max-Age / Expires | 有効期限を決める | 無指定だとブラウザを閉じるまで(セッションCookie) |
Cookie 自体は「認証方式」ではなく、データを往復させる運搬手段です。中に何を入れるか――「サーバを引く鍵(セッションID)」なのか「署名済みの本体(JWT)」なのか――が、次の論点です。
セッション vs JWT:正体をどこに置くか
ログイン後の「あなたは誰か」という情報をどこに保管するかで、2つの方式に分かれます。
- サーバセッション:正体(ユーザーID・権限など)はサーバ側に保存。ブラウザには中身のないランダムなID(セッションID)だけを渡す。IDはサーバ内のデータを引く「ロッカーの鍵」。
- JWT(JSON Web Token):正体そのものを署名付きで本人に持たせる。サーバは保管せず、届いたトークンの署名を検証するだけで本人と分かる。
// JWT は 3 つの部分を「.」で連結(ヘッダ.ペイロード.署名)
// xxxxx.yyyyy.zzzzz
// ・ヘッダ : 署名アルゴリズム(例 HS256)
// ・ペイロード: { "sub": "u123", "role": "admin", "exp": 1717400000 }
// ・署名 : 上記2つをサーバの秘密鍵で署名 → 改ざんすると検証に落ちる
ペイロードは Base64URL でエンコードされているだけで、誰でもデコードして中身を読めます(jwt.io に貼れば即見える)。守られているのは「中身を勝手に書き換えられない」こと(改ざん検知)であって、秘匿ではありません。パスワードやクレジットカード番号など、見られて困るものをペイロードに入れてはいけません。
両者の性質は、ほぼ正反対です。
| 観点 | サーバセッション(セッションID) | JWT(トークン) |
|---|---|---|
| 状態の置き場所 | サーバ側(DB / Redis 等) | クライアント側(トークン自体) |
| サーバの負担 | 保存と参照のストアが要る | ストア不要(署名検証だけ=ステートレス) |
| スケール | 複数サーバ間でセッション共有が必要 | どのサーバでも検証でき、水平分割しやすい |
| 強制ログアウト | サーバ側を消せば即無効化できる | 原則できない(有効期限切れまで生きる) |
| 向いている場面 | Webアプリ、確実な失効が要る場面 | API / マイクロサービス / SPA・モバイル |
JWT は中身だけで検証が完結する=サーバが状態を持たない分、一度発行したトークンを後から無効化しづらいのが最大の弱点です。「乗っ取られたので今すぐ全端末からログアウト」が苦手。対策は (1) 有効期限を短くする、(2) 失効リストや再認証用の Refresh Token をサーバ側で管理する、ですが、後者は「ステートレスにしたいのにサーバ状態を持つ」という本末転倒になりがち。確実な失効が要件ならセッションの方が素直です。
つまずきポイント:保存場所と XSS / CSRF
「JWT をどこに保存するか」で迷うのは定番です。HttpOnly Cookie か、localStorage か。これはどの攻撃に弱くなるかのトレードオフで、安易に決めると穴になります。
- XSS(クロスサイトスクリプティング):攻撃者の JS をページ内で実行される攻撃。
localStorageのトークンは JS から読めるので丸ごと盗まれる。一方HttpOnlyCookie は JS から読めないので、トークン本体は抜かれにくい。 - CSRF(クロスサイトリクエストフォージェリ):利用者がログイン中の別サイト(罠ページ)から、本物サイトへ勝手にリクエストを飛ばす攻撃。Cookie は自動で付くため成立しうる。
localStorageは手動で付ける(自動で付かない)ため、この攻撃には強い。
| 保存場所 | XSS への強さ | CSRF への強さ | 備考 |
|---|---|---|---|
| HttpOnly Cookie | 強い(JSから読めない) | 弱い → SameSite / トークンで対策 | Web アプリ向けの基本形 |
| localStorage | 弱い(盗まれたら終わり) | 強い(自動送信されない) | Authorization ヘッダに手動付与 |
「Cookie は CSRF があるから localStorage の方が安全」とよく言われますが、逆の穴(XSS)が開くだけです。実務では HttpOnly + Secure + SameSite の Cookie を基本にし、CSRF は SameSite=Lax/Strict や CSRF トークンで塞ぐ構成が手堅い。どこに置いても、XSS を出さないこと(入力のエスケープ等)が大前提である点は変わりません。
認証の流れ(最小例)
サーバセッション方式の往復を、ログインから保護ページ表示まで擬似コードで追います。
// 1) ログイン:本人確認できたらセッションを作り、ID を Cookie で渡す
app.post('/login', async (req, res) => {
const user = await verifyPassword(req.body.id, req.body.password);
if (!user) return res.status(401).send('Unauthorized'); // 401 = 誰か不明
const sid = createSession(user.id); // サーバ側ストアに { sid → user.id } を保存
res.cookie('session_id', sid, { httpOnly: true, secure: true, sameSite: 'lax' });
res.send('ok');
});
// 2) 保護ページ:届いた Cookie の ID でサーバ側を引き直す
app.get('/mypage', (req, res) => {
const userId = lookupSession(req.cookies.session_id); // ストアから逆引き
if (!userId) return res.status(401).send('Login required');
res.send(`Hello user ${userId}`);
});
401(未認証=誰か不明)と 403(認証済みだが権限なし)の違いは、HTTP のステータスコードで押さえたとおり。ログインを促すのが 401、ログイン済みでも立入禁止なのが 403 です。
「同一オリジンの普通の Web アプリ」「即ログアウトや権限剥奪を確実にやりたい」→ サーバセッション。「複数サービスをまたぐ API」「サーバを状態レスにしてスケールさせたい」「SPA / モバイルから叩く」→ JWT。両者の併用(JWT を HttpOnly Cookie に入れる等)も実務では普通です。
関連トピックとして、別オリジンの API を JS から叩くときに立ちはだかる CORS、そして認証情報を狙う攻撃面そのものは JavaScript や DOM の理解とも地続きです。
Web/フロントエンド Article
Cookie・セッション・JWT(Web認証)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
認証
比較で見る軸
難易度: intermediate / カテゴリ: Web/フロントエンド / タグ数: 4
導入後に効く点
状態の持ち方は2系統。サーバ側に正体を置く「セッション(セッションID)」と、署名済みの情報を本人に持たせる「JWT(トークン)」。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- Web/フロントエンド
- タグ数
- 4
判断チェックリスト
- 自社の用途が「認証 / Cookie」に近いか確認する。
- 強みである「HTTP はステートレス(前回を覚えない)。だから「ログイン済み」を伝える仕掛けが必要で、その運び役が Cookie。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。