Реализовывал ли авторизацию и аутентификацию на 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) для предотвращения брутфорса.

Ответ 18+ 🔞

А, ну это ж классика, ёпта! Каждый второй проект с этим сталкивается — как бы так пользователей пустить, но чтоб не натворили делов. Сам перепробовал кучу вариантов, сейчас разложу по полочкам, без воды.

1. JWT — наш бронепоезд для 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' });
  }
});

А потом на каждый защищённый роут ставишь такого вышибалу:

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-подход. Бывает, что JWT — это овердохуища сложности, а тебе нужно просто хранить состояние на сервере. Тогда express-session с Redis в помощь. Традиционно, надёжно, но свой геморрой есть.

3. OAuth 2.0 / OpenID Connect — когда лень свои формы городить. Пусть Google или GitHub сами аутентифицируют. passport.js — наш проводник в этот мир. Настраиваешь стратегию, и народ входит через сторонние аккаунты. Удобно, пользователям нравится, доверия ебать ноль к тебе лично не нужно.

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. Никаких MD5, ёпта, это же не 2002-й год на дворе.
  • HTTPS в продакшене — обязательно. Иначе все твои токены как открытка.
  • Куки для сессий — с флагами HttpOnly, Secure, SameSite. Чтобы не украли через какой-нибудь JS.
  • Секретные ключи (типа JWT_SECRET) — храни в переменных окружения, регулярно меняй и не коммить в репу, ядрёна вошь!
  • Rate limiting на эндпоинты входа — чтобы боты не устроили тебе брутфорс-вечеринку.

Вот, собственно, и вся магия. Главное — не выебывайся со сложными схемами, если проект маленький. Бери что проще и надёжнее. А то знаю я этих архитекторов, которые на пет-проекте OAuth с PKCE внедряют — мудя сплошная.