一覧へ

「セッションと認証 — Express Session」

「Expressにおけるセッションベース認証の仕組み、Cookieとの関係、セッションストアの概念を学びます。」

中級
|
12
|
検証済み (2026-07)
セッションexpress-session認証ログインセッションストア
進捗0/55 (0%)

セッションと認証 — Express Session

このトピックを終えると

セッションが必要な理由を理解し、express-session を使用してログイン/ログアウトを実装できるようになり、セッションストアの概念とセキュリティ設定について理解できるようになります。


なぜセッションが必要なのか

Cookie のトピックで「HTTP はステートレス」であることを学びました。Cookie を使用して状態を維持できますが、Cookie には致命的な限界があります。

javascript
// 良くない — ユーザー情報を Cookie に直接保存
res.cookie('user', JSON.stringify({ id: 1, role: 'admin' }));

クライアント(ブラウザ)は Cookie を自由に修正できます。ユーザーが role: 'admin'role: 'superadmin' に変更して送信した場合、サーバーはそれを区別できません。

セッションは、この問題を解決します。機密データは サーバーに保存 し、クライアントには 識別キー(セッション ID) のみ渡します。

text
Cookie 方式:クライアントに「名前=Hoon、役割=admin」を保存(改ざん可能)
セッション方式:クライアントに「セッションID=abc123」のみ保存 → サーバーが abc123 でユーザー情報を照会

セッションの動作原理

text
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 のインストールと設定

bash
npm install express-session
javascript
const 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絶対にコードにハードコーディングしないでください。環境変数で管理します。

javascript
secret: process.env.SESSION_SECRET || 'fallback-dev-only'

ログイン/ログアウトの実装

javascript
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() はサーバーのセッションデータを削除します。


認証ミドルウェア

保護されたルートにアクセスするたびに「ログインしているか?」を確認する必要があります。これをミドルウェアにすると、すべてのルートで再利用できます。

javascript
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 などの外部ストレージを使用します。

bash
npm install connect-redis redis
javascript
const 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 サーバー、モバイルアプリ、マイクロサービス
text
従来の Web アプリ (SSR) → セッションが自然
SPA + API サーバー → JWT が便利
モバイルアプリ → JWT(Cookie の管理が複雑)

重要なまとめ

概念まとめ
セッションユーザーデータをサーバーに保存し、クライアントには ID のみ渡す
セッション IDCookie で渡される識別キー (connect.sid)
req.sessionセッションデータを読み書きするオブジェクト
destroy()ログアウト — セッションデータを削除
セッションストア本番環境では Redis などの外部ストレージを使用

セッションベースの認証の核心:機密データはサーバーに、クライアントには鍵(セッション ID)のみ。この原則を理解すると、トークンベースの認証(JWT)との違いも自然に理解できます。


💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...