Реализовывал ли авторизацию и аутентификацию на Node.js

«Реализовывал ли авторизацию и аутентификацию на Node.js» — вопрос из категории Node.js Core, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, реализовывал различные схемы аутентификации и авторизации в приложениях на Node.js.

1. JWT (JSON Web Tokens) для stateless-аутентификации. Это наиболее частый выбор для RESTful API и микросервисов. Использую библиотеки jsonwebtoken для подписи/верификации и bcrypt для хэширования паролей.

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

// Эндпоинт логина
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await UserModel.findOne({ email });

    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
    if (!isPasswordValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign(
      { 
        userId: user.id,
        role: user.role // Включаем роль для авторизации
      },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({ token, userId: user.id });
  } catch (error) {
    res.status(500).json({ error: 'Login failed' });
  }
});

Защита роутов с помощью middleware:

const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1]; // Bearer <token>
  if (!token) return res.status(401).send('Access denied');

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // Добавляем данные пользователя в запрос
    next();
  } catch (err) {
    res.status(400).send('Invalid token');
  }
};

app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({ profile: '...', userId: req.user.userId });
});

2. Сессии (stateful). Использовал express-session с хранилищем в Redis для традиционных веб-приложений, где нужны сессии.

3. OAuth 2.0 / OpenID Connect. Интегрировал сторонние провайдеры (Google, GitHub) с помощью passport.js. Например, для аутентификации через Google:

const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: '/api/auth/google/callback'
  },
  async (accessToken, refreshToken, profile, done) => {
    // Находим или создаем пользователя в нашей БД на основе profile.id
    const user = await findOrCreateUser(profile);
    done(null, user);
  }
));

4. Авторизация на основе ролей (RBAC). После аутентификации проверял права доступа. Часто создавал второй middleware для авторизации:

const authorize = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user || !allowedRoles.includes(req.user.role)) {
      return res.status(403).send('Forbidden');
    }
    next();
  };
};

// Только админы могут удалять пользователей
app.delete('/api/users/:id', authMiddleware, authorize('admin'), deleteUserHandler);

Ключевые практики безопасности, которые я соблюдаю:

  • Всегда храню пароли в виде хэшей (использую bcrypt с достаточным фактором стоимости).
  • Использую HTTPS в продакшене.
  • Устанавливаю безопасные флаги для куков (HttpOnly, Secure, SameSite) при работе с сессиями.
  • Регулярно обновляю секретные ключи (JWT_SECRET) и использую разные ключи для разных сред.
  • Ограничиваю частоту попыток входа (rate limiting) для предотвращения брутфорса.