Соответствуют ли сессии принципам REST в контексте Node.js API?

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

Ответ

Нет, сессии на сервере нарушают ключевой принцип REST — stateless (без состояния). В RESTful API на Node.js каждый запрос должен содержать всю необходимую информацию для аутентификации и авторизации.

Проблемы с сессиями в REST:

  1. Нарушение stateless — сервер хранит состояние сессии
  2. Проблемы с масштабированием — сессии должны реплицироваться между серверами
  3. Сложность кэширования — ответы не могут кэшироваться, если они зависят от сессии

Вместо сессий в Node.js REST API я использую:

JWT (JSON Web Tokens):

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();

// Генерация токена
app.post('/login', (req, res) => {
  const user = authenticate(req.body);
  const token = jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
  res.json({ token });
});

// Middleware для проверки
const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// Защищённый endpoint
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({ userId: req.user.userId });
});

Альтернативы:

  • OAuth 2.0 / OpenID Connect для сторонней аутентификации
  • API keys для сервис-сервисного взаимодействия
  • Signed cookies с httpOnly и secure флагами

Если нужны сессии (например, для традиционных веб-приложений), я использую express-session с хранилищем в Redis для масштабирования:

const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false
}));

Но это уже не чистый REST, а гибридный подход.