Passport.js: обзор
Passport.js — middleware для аутентификации в Node.js/Express, предоставляющий унифицированный API для подключения различных стратегий входа: Local (логин/пароль), JWT, OAuth (Google, GitHub, Facebook).
Зачем нужно
Реализация аутентификации с нуля сложна и подвержена ошибкам безопасности. Passport стандартизирует этот процесс через концепцию стратегий: переключение с Local на Google OAuth требует лишь подключения другой стратегии без изменения остального кода. Экосистема включает 500+ готовых стратегий.
Где используется
- Классическая веб-аутентификация через форму логина (Local Strategy)
- JWT-аутентификация для REST API (passport-jwt)
- Social login: Google, GitHub, Facebook, Twitter (OAuth 2.0)
- SSO через SAML (enterprise приложения)
Основной контент
Установка (Local + JWT стратегии)
npm install passport passport-local passport-jwt jsonwebtoken
npm install express-session # для Local стратегии с сессиями
Local Strategy — логин/пароль
// auth/passport.js
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const User = require('../models/User');
passport.use(new LocalStrategy(
{ usernameField: 'email' }, // по умолчанию 'username'
async (email, password, done) => {
try {
const user = await User.findOne({ email });
if (!user) return done(null, false, { message: 'User not found' });
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return done(null, false, { message: 'Invalid password' });
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.js
const session = require('express-session');
const passport = require('passport');
require('./auth/passport');
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
app.use(passport.initialize);
app.use(passport.session);
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login' }),
(req, res) => res.redirect('/dashboard')
);
app.get('/logout', (req, res) => {
req.logout( => res.redirect('/'));
});
// Middleware защиты маршрута
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated) return next;
res.redirect('/login');
}
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.json({ user: req.user });
});
JWT Strategy — для REST API
// auth/passport.js (добавляем JWT стратегию)
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
passport.use(new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken,
secretOrKey: process.env.JWT_SECRET
},
async (payload, done) => {
try {
const user = await User.findById(payload.sub);
if (!user) return done(null, false);
return done(null, user);
} catch (err) {
return done(err);
}
}
));
// routes/auth.js
const jwt = require('jsonwebtoken');
// Логин — выдать JWT
router.post('/login',
passport.authenticate('local', { session: false }),
(req, res) => {
const token = jwt.sign(
{ sub: req.user.id, email: req.user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token });
}
);
// Защищённый маршрут
const jwtAuth = passport.authenticate('jwt', { session: false });
router.get('/profile', jwtAuth, (req, res) => {
res.json(req.user);
});
Callback-результаты стратегии
// done(error, user, info)
done(null, user) // успешная аутентификация
done(null, false) // неверные данные (не ошибка)
done(null, false, { message: 'Wrong password' }) // с сообщением
done(new Error('DB error')) // системная ошибка
Частые ошибки
- Сессии в REST API — для stateless API использовать JWT с
{ session: false }, не настраивать express-session - Не хешировать пароль — хранить пароль в открытом виде; всегда использовать bcrypt
- JWT без истечения срока —
expiresInобязателен; без него токен действует вечно req.userнедоступен — не подключилиpassport.initializeиpassport.sessionили не требуют аутентификацию для маршрута