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 или не требуют аутентификацию для маршрута

Связанные темы

Ресурсы