Passport.js — 認証ミドルウェア
このトピックを終えると
Passport.js がなぜ必要なのかを理解し、Local Strategy を使用して ID/パスワード認証を実装できるようになり、serialize/deserialize の役割を説明できるようになります。
なぜ Passport.js を使うのか
前のトピックで、セッションベースの認証を直接実装しました。それは機能しますが、認証方式が多様になるとコードが複雑になります。
- ID/パスワードログイン
- Google ログイン
- GitHub ログイン
- JWT トークン認証
それぞれを直接実装すると、ルートごとに認証ロジックが分散してしまいます。Passport.js は、この問題を解決する認証専門ミドルウェアです。500 種類以上の認証ストラテジー (Strategy) をプラグイン形式で提供します。
主要な概念 — Strategy パターン
Passport.js は Strategy パターンを使用します。「どのように認証するか」を Strategy オブジェクトに分離します。
Passport.js (フレームワーク)
├── Local Strategy → ID/パスワード
├── Google Strategy → Google OAuth
├── GitHub Strategy → GitHub OAuth
└── JWT Strategy → JSON Web Token各 Strategy は独立した npm パッケージです。必要なものだけインストールして使用します。
インストール
npm install passport passport-local express-sessionpassport: コアライブラリpassport-local: ID/パスワード認証ストラテジーexpress-session: セッション管理 (Passport が内部で使用)
Local Strategy の設定
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const app = express();
// 仮想のユーザー DB
const users = [
{ id: 1, username: 'alice', password: 'pass123', displayName: 'Alice Kim' },
{ id: 2, username: 'bob', password: 'pass456', displayName: 'Bob Lee' }
];
// 1. Strategy を登録
passport.use(new LocalStrategy(
(username, password, done) => {
const user = users.find(u => u.username === username);
if (!user) {
return done(null, false, { message: 'User not found' });
}
if (user.password !== password) {
return done(null, false, { message: 'Wrong password' });
}
return done(null, user);
}
));done コールバックの 3 つの呼び出しパターン:
| 呼び出し | 意味 |
|---|---|
done(null, user) | 認証成功。user オブジェクトを渡す |
done(null, false, {message}) | 認証失敗。ユーザーが存在しない、またはパスワードが一致しない |
done(err) | システムエラー。DB への接続失敗など |
Serialize / Deserialize
セッションにユーザー全体のオブジェクトを保存すると、メモリを無駄にします。Passport は、セッションに 最小限の識別情報 (通常は ID) のみを保存し、リクエストごとにその ID を使用してユーザーを復元します。
// 2. セッションに保存するデータ (ログイン時に 1 回)
passport.serializeUser((user, done) => {
done(null, user.id); // セッションに user.id のみ保存
});
// 3. セッションからユーザーを復元 (各リクエストごと)
passport.deserializeUser((id, done) => {
const user = users.find(u => u.id === id);
done(null, user); // req.user にユーザーオブジェクト全体を設定
});ログイン時: user オブジェクト → serializeUser → セッションに id=1 を保存
各リクエスト時: セッションから id=1 → deserializeUser → user オブジェクトを復元 → req.userdeserializeUser で DB をクエリすると、セッションには ID のみ保存しながらも、各リクエストで最新のユーザー情報を利用できます。
ミドルウェアの接続
// 4. ミドルウェアの設定 (順序が重要!)
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
secret: process.env.SESSION_SECRET || 'dev-secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize()); // Passport を初期化
app.use(passport.session()); // セッションと接続順序が重要です: express-session → passport.initialize() → passport.session()。順序が異なると、セッションが正しく機能しません。
ログイン / ログアウト ルート
// ログイン
app.post('/login',
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureMessage: true
})
);
// または JSON 応答を希望する場合
app.post('/api/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.status(401).json({ error: info.message });
}
req.logIn(user, (err) => {
if (err) return next(err);
res.json({ message: `Welcome, ${user.displayName}` });
});
})(req, res, next);
});
// ログアウト
app.post('/logout', (req, res) => {
req.logout((err) => {
if (err) return res.status(500).json({ error: 'Logout failed' });
res.json({ message: 'Logged out' });
});
});passport.authenticate('local') は、登録済みの LocalStrategy を実行します。成功すると serializeUser が呼び出され、セッションにユーザー ID が保存されます。
保護されたルート
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ error: 'Login required' });
}
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.json({
message: `Hello, ${req.user.displayName}`,
user: { id: req.user.id, username: req.user.username }
});
});req.isAuthenticated() は Passport が提供するメソッドで、セッションに有効なユーザーが存在する場合は true を返します。req.user は、deserializeUser で復元されたユーザーオブジェクトです。
全体の流れを整理
1. サーバーの起動
passport.use(LocalStrategy) — 認証方法を登録
passport.serializeUser() — セッションに保存する情報を登録
passport.deserializeUser() — セッションから情報を復元する方法を登録
2. POST /login
passport.authenticate('local') — Strategy を実行
→ 成功: serializeUser → セッションに ID を保存 → 応答
→ 失敗: 401 応答
3. GET /dashboard (認証が必要)
セッションクッキーを確認 → deserializeUser → req.user を設定
ensureAuthenticated → req.isAuthenticated() → true → ルートを実行
4. POST /logout
req.logout() → セッションからユーザーを削除 → 応答よくある間違い
| 間違い | 結果 | 解決 |
|---|---|---|
app.use(session(...)) の前に passport.session() を呼び出す | セッションが初期化されない | express-session → passport.initialize() → passport.session() の順序 |
serializeUser でユーザーオブジェクト全体を保存する | セッションサイズが膨大になる | user.id のみを保存 |
deserializeUser で非同期エラーを無視する | 認証失敗時にサーバーがクラッシュする | done(err) を呼び出す |
Strategy コールバックで done() の呼び出しを忘れる | リクエストが無限に待機する | すべての分岐で done() が呼び出されていることを確認する |
主要なまとめ
| 概念 | まとめ |
|---|---|
| Passport.js | 認証専門ミドルウェア。Strategy パターンでさまざまな認証方式をサポート |
| Strategy | 認証方法をカプセル化されたオブジェクト (Local, Google, JWT など) |
| serializeUser | セッションに保存する最小限の情報を決定する (通常は user.id) |
| deserializeUser | 各リクエストごとにセッションの ID からユーザーオブジェクトを復元する |
req.user | deserializeUser で復元された、現在のログインユーザー |
req.isAuthenticated() | ログイン状態を確認する |
Passport.js の価値は、「認証ロジックの標準化」にあります。Local から Google OAuth に変更する場合、Strategy のみを置き換えるだけで済みます。serialize/deserialize、ミドルウェアチェーン、req.user — それ以外のコードはそのまま維持できます。