セッションと認証 — Express Session
このトピックを終えると
セッションが必要な理由を理解し、express-session を使用してログイン/ログアウトを実装できるようになり、セッションストアの概念とセキュリティ設定について理解できるようになります。
なぜセッションが必要なのか
Cookie のトピックで「HTTP はステートレス」であることを学びました。Cookie を使用して状態を維持できますが、Cookie には致命的な限界があります。
// 良くない — ユーザー情報を Cookie に直接保存
res.cookie('user', JSON.stringify({ id: 1, role: 'admin' }));クライアント(ブラウザ)は Cookie を自由に修正できます。ユーザーが role: 'admin' を role: 'superadmin' に変更して送信した場合、サーバーはそれを区別できません。
セッションは、この問題を解決します。機密データは サーバーに保存 し、クライアントには 識別キー(セッション ID) のみ渡します。
Cookie 方式:クライアントに「名前=Hoon、役割=admin」を保存(改ざん可能)
セッション方式:クライアントに「セッションID=abc123」のみ保存 → サーバーが abc123 でユーザー情報を照会セッションの動作原理
1. ログイン要求
Client → Server: POST /login {username: "Hoon", password: "****"}
2. サーバーがセッションを生成
Server: セッションストアに {userId: 1, role: "admin"} を保存
Server: セッション ID = "abc123" を生成
Server → Client: Set-Cookie: connect.sid=abc123
3. その後の要求
Client → Server: GET /dashboard (Cookie: connect.sid=abc123)
Server: abc123 でセッションを照会 → {userId: 1, role: "admin"} を確認
Server → Client: ダッシュボードデータクライアントはセッション ID のみを知っており、実際のデータ(userId、role)はサーバーのメモリまたはデータベースにあります。クライアントがセッション ID を偽造しても、サーバーのセッションストアに該当する ID がなければ認証は失敗します。
express-session のインストールと設定
npm install express-sessionconst express = require('express');
const session = require('express-session');
const app = express();
app.use(express.json());
app.use(session({
secret: 'my-secret-key-change-this',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // 本番環境では true
maxAge: 3600000 // 1 hour
}
}));| オプション | 説明 |
|---|---|
secret | セッション ID を暗号化するキー。長くランダムに設定 |
resave | 変更がなくてもすべてのリクエストごとにセッションを保存するか。false を推奨 |
saveUninitialized | 空のセッションも保存するか。false にするとログイン前には Cookie を発行しない |
cookie | セッション Cookie オプション(httpOnly、secure、maxAge など) |
secret は 絶対にコードにハードコーディングしないでください。環境変数で管理します。
secret: process.env.SESSION_SECRET || 'fallback-dev-only'ログイン/ログアウトの実装
const users = [
{ id: 1, username: 'alice', password: 'pass123', role: 'admin' },
{ id: 2, username: 'bob', password: 'pass456', role: 'user' }
];
// ログイン
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// セッションにユーザー情報を保存
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: `Welcome, ${user.username}` });
});
// ログアウト
app.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
});req.session はオブジェクトです。どのようなプロパティでも自由に保存できます。req.session.destroy() はサーバーのセッションデータを削除します。
認証ミドルウェア
保護されたルートにアクセスするたびに「ログインしているか?」を確認する必要があります。これをミドルウェアにすると、すべてのルートで再利用できます。
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Login required' });
}
next();
}
function requireAdmin(req, res, next) {
if (req.session.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
}
// 使用
app.get('/profile', requireAuth, (req, res) => {
res.json({ userId: req.session.userId, role: req.session.role });
});
app.get('/admin/dashboard', requireAuth, requireAdmin, (req, res) => {
res.json({ message: 'Admin dashboard' });
});ミドルウェアをチェーンして、「ログイン + 管理者」のように複合条件を作成できます。
セッションストア — メモリ vs 外部ストレージ
デフォルトでは、express-session は サーバーのメモリ にセッションを保存します。開発には十分ですが、本番環境では問題が発生します。
| 問題 | 説明 |
|---|---|
| サーバー再起動 | メモリが初期化されるため、すべてのユーザーがログアウトする |
| メモリリーク | セッションが蓄積すると、サーバーのメモリが不足する |
| 複数サーバー | サーバー A のメモリにあるセッションを、サーバー B が知らない |
本番環境では、Redis、MongoDB、PostgreSQL などの外部ストレージを使用します。
npm install connect-redis redisconst RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient();
redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));Redis にセッションを保存すると、サーバーが再起動してもセッションが維持され、複数のサーバーが同じ Redis を参照するとセッションを共有できます。
セッション vs JWT — いつ何を
| セッション | JWT | |
|---|---|---|
| 状態保存 | サーバー (stateful) | クライアント (stateless) |
| スケーリング | Redis などの共有ストレージが必要 | サーバー間の共有は不要 |
| ログアウト | destroy() で即座に無効化 | トークンの有効期限まで有効(ブラックリストが必要) |
| セキュリティ | サーバーがデータを管理 | トークンが盗まれた場合の対応が困難 |
| 適切なユースケース | 従来の Web アプリ、管理者ページ | API サーバー、モバイルアプリ、マイクロサービス |
従来の Web アプリ (SSR) → セッションが自然
SPA + API サーバー → JWT が便利
モバイルアプリ → JWT(Cookie の管理が複雑)重要なまとめ
| 概念 | まとめ |
|---|---|
| セッション | ユーザーデータをサーバーに保存し、クライアントには ID のみ渡す |
| セッション ID | Cookie で渡される識別キー (connect.sid) |
req.session | セッションデータを読み書きするオブジェクト |
destroy() | ログアウト — セッションデータを削除 |
| セッションストア | 本番環境では Redis などの外部ストレージを使用 |
セッションベースの認証の核心:機密データはサーバーに、クライアントには鍵(セッション ID)のみ。この原則を理解すると、トークンベースの認証(JWT)との違いも自然に理解できます。