Какие плюсы и минусы у JSON Web Tokens (JWT) для аутентификации в Node.js приложениях?

Ответ

Плюсы JWT в Node.js:

  • Stateless (без состояния): Серверу не нужно хранить сессию в памяти или БД. Это упрощает архитектуру и горизонтальное масштабирование: любой инстанс приложения может проверить токен.
  • Самодостаточность: В payload токена можно хранить базовые данные о пользователе (userId, роль), уменьшая количество запросов к БД.
  • Безопасность подписи: Использование алгоритмов вроде HS256 или RS256 гарантирует, что токен не был изменен после выдачи.
  • Универсальность: Легко использовать между различными доменами (CORS) и типами клиентов (веб, мобильные приложения, микросервисы).

Минусы и риски JWT:

  • Немедленный отзыв невозможен: Токен валиден до истечения срока (exp). Для решения используют короткоживущие access-токены (минуты) и долгоживущие refresh-токены, которые можно отозвать на сервере.
  • Размер: Токен больше, чем session ID, и передается с каждым запросом, что увеличивает трафик.
  • Уязвимость к XSS при неправильном хранении: Если хранить JWT в localStorage, он доступен для скриптов. Более безопасный способ — использовать httpOnly куки (хотя это усложняет работу с мобильными приложениями).
  • Данные устаревают: Информация в payload может не соответствовать актуальным данным в БД (например, если роль пользователя изменилась).

Пример реализации с библиотекой jsonwebtoken:

// Генерация токена
const jwt = require('jsonwebtoken');
const accessToken = jwt.sign(
  { userId: 123, role: 'user' }, // payload
  process.env.JWT_SECRET,        // секретный ключ
  { expiresIn: '15m' }           // срок жизни
);

const refreshToken = jwt.sign(
  { userId: 123 },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// Middleware для проверки токена в Express
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403); // Невалидный или просроченный токен
    req.user = user; // Добавляем данные пользователя в объект запроса
    next();
  });
}

app.get('/protected', authenticateToken, (req, res) => {
  res.json({ message: `Hello user ${req.user.userId}` });
});

Ответ 18+ 🔞

Слушай, а вот про эти JWT токены — история, конечно, интересная. Как будто на дворе 2002-й год, все снова влюбились в идею «без состояния», только теперь с подписью. Ну, давай разберём, что к чему, без соплей.

Плюсы, которые все так любят:

  • Stateless, то есть без состояния. Это главный козырь, ёпта. Серверу похуй, он не держит в памяти, кто ты такой. Получил запрос, проверил подпись токена — и всё, свободен. Хоть тысячу инстансов запускай, каждый сам по себе, и всем похуй друг на друга. Масштабируется — просто ебушки-воробушки, а не архитектура.
  • Сам себе режиссёр. Внутри токена можно запихнуть базовую инфу: userId, роль там какую-нибудь. Получилось — не надо при каждом чихе лезть в базу, что экономит время и нервы. Хотя, если честно, доверия ебать ноль к таким данным, потому что они могут устареть, но об этом позже.
  • Подпись. Токен подписан, его не подделаешь просто так. Если секрет не слили, то жить можно. Это не та сессионная печенька, которую можно подобрать.
  • Универсальный солдат. Отдал токен клиенту — и пусть таскает его хоть с мобилы, хоть из браузера, хоть другому микросервису кидает. CORS обычно не ругается. Удобно, чё.

Но минусы, блядь, такие, что волосы дыбом:

  • Главная проблема — отозвать его нихуя нельзя. Выдал токен на неделю — он и будет летать неделю, даже если пользователя уже забанили. Представь: «Иди ты нахуй!» — сказал админ, а токен у чувака ещё живёт и здравствует. Решение? Делать access-токены короткоживущими (типа на 15 минут), а для обновления давать отдельный refresh-токен, который уже можно прибить на сервере. Но это уже геморрой.
  • Размер. Это же не просто ID сессии. Это целая строка, закодированная в Base64, овердохуища текста. И таскать её в каждом запросе — не самый эффективный способ тратить трафик.
  • Хранение — головная боль. Если тупо засунуть в localStorage, то любой XSS-скрипт его вытащит. Удивление пиздец, когда узнаёшь, что твой токен уже гуляет по просторам даркнета. Более-менее безопасно — httpOnly куки, но тогда с мобильными приложениями начинается ёперный театр.
  • Данные в нём — как прошлогодний снег. Записал в токен роль user, а потом админ повысил его до moderator. Так он до самого конца срока жизни токена будет ходить с устаревшей ролью. Подозрение ебать чувствую к такому подходу.

Ну и как обычно, пример кода, чтобы было понятно, о чём речь:

// Генерация токена
const jwt = require('jsonwebtoken');
const accessToken = jwt.sign(
  { userId: 123, role: 'user' }, // payload
  process.env.JWT_SECRET,        // секретный ключ
  { expiresIn: '15m' }           // срок жизни
);

const refreshToken = jwt.sign(
  { userId: 123 },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// Middleware для проверки токена в Express
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer <token>

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403); // Невалидный или просроченный токен
    req.user = user; // Добавляем данные пользователя в объект запроса
    next();
  });
}

app.get('/protected', authenticateToken, (req, res) => {
  res.json({ message: `Hello user ${req.user.userId}` });
});

Короче, инструмент мощный, но вы ходите по охуенно тонкому льду. Если накосячить с хранением, сроками жизни или секретами — будет вам хиросима, а не авторизация. Используй с умом, и всё будет пучком.