はじめに
Passportとは
Passportは、Node.jsアプリケーションに認証機能を追加するためのミドルウェアです。Express.jsと組み合わせて使用することで、ユーザー認証の実装を簡単かつ柔軟に行えます。
なぜPassportを使うのか
認証機能を一から実装するのは複雑で、セキュリティリスクも伴います。Passportを使うことで以下のメリットが得られますね。
- 認証ロジックの標準化
- セキュリティのベストプラクティスの適用
- 複数の認証方式への柔軟な対応
- セッション管理の自動化
Passportの基本概念
認証ミドルウェアとしての役割
PassportはExpressのミドルウェアとして動作し、リクエストとレスポンスの間に入って認証処理を実行します。
Strategyパターン
Passportでは「Strategy」という概念を使って認証方式を定義します。これにより、同じインターフェースで異なる認証方式を扱えるようになっています。
セッションベース認証の仕組み
Passportはセッションを使ってログイン状態を維持します。ユーザーがログインすると、セッションにユーザー情報が保存され、以降のリクエストで自動的に復元されます。
Passportが提供する主要機能
※コードの実行はご自身の責任でお願いします。
認証戦略(Strategy)
Passportは500以上の認証戦略をサポートしており、プロジェクトの要件に応じて選択できます。
ローカル認証
ユーザー名とパスワードを使った従来型の認証方式です。
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
const user = await User.findOne({ username });
if (!user) {
return done(null, false, { message: 'ユーザーが見つかりません' });
}
if (!user.verifyPassword(password)) {
return done(null, false, { message: 'パスワードが正しくありません' });
}
return done(null, user);
} catch (err) {
return done(err);
}
}
));
OAuth認証
Google、Facebook、Twitterなどのソーシャルログインをサポートしています。
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
// ユーザー情報の処理
return done(null, profile);
}));
JWT認証
ステートレスな認証が必要な場合に使用します。APIサーバーでよく採用される方式ですね。
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
passport.use(new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET
}, (payload, done) => {
User.findById(payload.sub, (err, user) => {
if (err) return done(err, false);
if (user) return done(null, user);
return done(null, false);
});
}));
セッション管理
Passportのセッション管理は、serializeUserとdeserializeUserの2つの関数で実現されます。
serializeUserの役割
ログイン時にユーザー情報をセッションに保存する処理を定義します。通常はユーザーIDのみを保存することで、セッションのデータ量を最小限に抑えます。
passport.serializeUser((user, done) => {
done(null, user.id);
});
deserializeUserの役割
リクエストごとにセッションからユーザー情報を復元する処理を定義します。保存されたユーザーIDを使ってデータベースから完全なユーザー情報を取得します。
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (err) {
done(err);
}
});
認証状態の永続化
セッションを使うことで、ユーザーがブラウザを閉じても一定期間ログイン状態を維持できます。
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7 // 7日間
}
}));
認証フロー
ログイン処理
passport.authenticate()を使ってログイン処理を実装します。
app.post('/login',
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true
})
);
コールバック形式でより細かい制御も可能です。
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.status(401).json({ message: info.message });
}
req.logIn(user, (err) => {
if (err) return next(err);
return res.json({ message: 'ログイン成功', user });
});
})(req, res, next);
});
ログアウト処理
req.logout()を呼び出すだけでログアウトできます。
app.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect('/');
});
});
認証状態の確認
req.isAuthenticated()でログイン状態を確認できます。
app.get('/profile', (req, res) => {
if (req.isAuthenticated()) {
res.render('profile', { user: req.user });
} else {
res.redirect('/login');
}
});
実装例:ローカル認証の基本
環境構築
まずは必要なパッケージをインストールしましょう。
npm install express passport passport-local express-session
必要なパッケージ
- express: Webアプリケーションフレームワーク
- passport: 認証ミドルウェア本体
- passport-local: ローカル認証戦略
- express-session: セッション管理
初期設定
基本的なサーバーセットアップを行います。
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const app = express();
// ミドルウェア設定
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// セッション設定
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
}));
// Passport初期化
app.use(passport.initialize());
app.use(passport.session());
Passport設定
Strategyの登録
ローカル認証戦略を登録します。
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
// ユーザー検索
const user = await User.findOne({ username });
if (!user) {
return done(null, false, {
message: 'ユーザー名が正しくありません'
});
}
// パスワード検証
const isValid = await user.validatePassword(password);
if (!isValid) {
return done(null, false, {
message: 'パスワードが正しくありません'
});
}
return done(null, user);
} catch (err) {
return done(err);
}
}
));
シリアライズ/デシリアライズの実装
セッション管理のための設定を行います。
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (err) {
done(err);
}
});
ルート実装
ログイン
ログインフォームの表示とログイン処理を実装します。
// ログインフォーム表示
app.get('/login', (req, res) => {
res.render('login');
});
// ログイン処理
app.post('/login',
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true
})
);
ログアウト
ログアウト処理を実装します。
app.post('/logout', (req, res, next) => {
req.logout((err) => {
if (err) return next(err);
res.redirect('/');
});
});
保護されたルート
認証が必要なルートを作成してみましょう。
// 認証チェックミドルウェア
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
}
// 保護されたルート
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.render('dashboard', { user: req.user });
});
ミドルウェアでの活用
req.userの利用
認証後、どのルートハンドラからでもreq.userでユーザー情報にアクセスできます。
app.get('/profile', ensureAuthenticated, (req, res) => {
res.json({
id: req.user.id,
username: req.user.username,
email: req.user.email
});
});
認証チェック
全てのビューでユーザー情報を利用可能にする設定です。
app.use((req, res, next) => {
res.locals.currentUser = req.user;
next();
});
これにより、テンプレート内でcurrentUser変数が使えるようになります。
<% if (currentUser) { %>
<p>ようこそ、<%= currentUser.username %>さん</p>
<a href="/logout">ログアウト</a>
<% } else { %>
<a href="/login">ログイン</a>
<% } %>
まとめ
Passportのメリット
Passportを使うことで、以下のような利点が得られます。
- シンプルな実装: 複雑な認証ロジックを抽象化
- 拡張性: 500以上の認証戦略から選択可能
- 保守性: 標準化されたパターンで可読性が向上
- セキュリティ: 実績のあるライブラリによる安全な実装
次のステップ
基本的なローカル認証を理解したら、以下のトピックに進んでみましょう。
- OAuth認証の実装(Google、GitHub等)
- パスワードリセット機能の追加
- 二段階認証の導入
- JWT認証への移行(API向け)
- セッションストアの最適化(Redis等)
Passportは柔軟性が高いため、プロジェクトの要件に合わせてカスタマイズしながら使っていくことができますね。