Передача данных из токена в req.user

При работе с аутентификацией в Node.js часто используется подход, при котором клиент передаёт токен в каждом запросе, а сервер извлекает из него данные пользователя и закрепляет их в объекте запроса. Это позволяет дальше по цепочке middleware и обработчиков обращаться к req.user без повторного разбора токена.

В экосистеме JavaScript для задач шифрования и защиты данных в токенах нередко используется библиотека Iron из пакета @hapi/iron. Она обеспечивает безопасное «упаковывание» и «распаковку» данных с использованием симметричного шифрования.


Роль req.user в архитектуре приложения

Объект req.user является стандартным способом хранения информации о текущем аутентифицированном пользователе в большинстве HTTP-фреймворков (Express, Koa через адаптеры и т.д.).

Типичное содержимое:

  • идентификатор пользователя
  • email или логин
  • роли и права доступа
  • дополнительные метаданные профиля

После успешной аутентификации вся эта информация должна быть доступна на уровне запроса без повторного обращения к базе данных.


Принцип работы Iron-токена

Библиотека Iron реализует механизм защищённой сериализации данных:

  • данные преобразуются в строку
  • выполняется шифрование с использованием ключа
  • добавляется HMAC для защиты от подмены
  • формируется компактный токен

При обратной операции происходит полная проверка целостности и расшифровка.


Базовая схема обработки запроса

Поток обработки обычно выглядит следующим образом:

  1. Клиент отправляет запрос с заголовком Authorization
  2. Middleware извлекает токен
  3. Токен «распаковывается» через Iron
  4. Полученные данные помещаются в req.user
  5. Последующие middleware используют req.user

Пример middleware для извлечения данных из Iron-токена

import Iron from '@hapi/iron';

const PASSWORD = process.env.IRON_PASSWORD;

export async function authMiddleware(req, res, next) {
  try {
    const header = req.headers.authorization;

    if (!header || !header.startsWith('Bearer ')) {
      req.user = null;
      return next();
    }

    const token = header.slice(7);

    const session = await Iron.unseal(
      token,
      PASSWORD,
      Iron.defaults
    );

    req.user = session.user;

    next();
  } catch (err) {
    req.user = null;
    next();
  }
}

Структура данных внутри токена

При создании токена обычно используется обёртка над пользовательскими данными:

const session = {
  user: {
    id: 42,
    email: 'user@example.com',
    role: 'admin'
  },
  createdAt: Date.now()
};

Далее этот объект «запечатывается»:

const sealed = await Iron.seal(
  session,
  PASSWORD,
  Iron.defaults
);

Полученная строка и передаётся клиенту.


Формирование req.user как центральной точки доступа

После прохождения middleware объект запроса получает структуру:

req.user = {
  id: 42,
  email: 'user@example.com',
  role: 'admin'
};

Дальнейшая логика приложения опирается исключительно на этот объект:

  • проверка прав доступа
  • фильтрация данных
  • персонализация ответов

Интеграция с Express

import express from 'express';
import { authMiddleware } from './authMiddleware.js';

const app = express();

app.use(authMiddleware);

app.get('/profile', (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  res.json({
    profile: req.user
  });
});

Разделение ответственности middleware

Типичная архитектура включает несколько слоёв:

1. Middleware извлечения токена

Отвечает только за получение строки из заголовка.

2. Middleware распаковки Iron

Преобразует токен в объект данных.

3. Middleware авторизации

Проверяет права доступа на основе req.user.


Обработка ошибок при расшифровке

Ошибки могут возникать по причинам:

  • неверный секретный ключ
  • истёкший токен (если реализовано вручную)
  • повреждённая строка токена
  • попытка подмены данных

Стандартная стратегия обработки — сброс пользователя:

catch (err) {
  req.user = null;
}

и передача управления дальше.


Связь Iron с JWT-подходом

Хотя JWT является более распространённым стандартом, Iron выполняет схожую задачу, но с отличиями:

  • данные полностью шифруются, а не только подписываются
  • отсутствует читаемая payload-структура
  • повышенная защита содержимого токена

В результате req.user формируется только после полной расшифровки.


Практика безопасного хранения данных в req.user

Не рекомендуется помещать в req.user:

  • пароли (даже хешированные)
  • чувствительные персональные данные
  • большие объёмы данных

Оптимальный подход — минимальный набор идентификаторов и ролей, остальное извлекается при необходимости.


Повторное использование req.user в сервисном слое

После формирования объекта он может передаваться дальше:

function getDashboardData(req) {
  const userId = req.user.id;

  return database.findDashboardByUserId(userId);
}

Это позволяет полностью исключить повторную дешифровку токена на уровне бизнес-логики.


Кэширование результата распаковки

При высоконагруженных системах допустимо кэширование результата Iron-дешифровки в рамках одного запроса:

  • распаковка выполняется один раз
  • результат сохраняется в req.user
  • дальнейшие middleware используют уже готовые данные

Использование строгой типизации (JSDoc / TypeScript)

При работе с req.user важно фиксировать структуру:

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} email
 * @property {string} role
 */

/** @type {User|null} */
req.user;

Это снижает вероятность ошибок при доступе к полям.


Расширение структуры пользователя

Иногда в токен помещается расширенный контекст:

const session = {
  user: {
    id: 42,
    email: 'user@example.com'
  },
  permissions: ['read', 'write'],
  deviceId: 'abc123'
};

После распаковки middleware может нормализовать структуру:

req.user = session.user;
req.permissions = session.permissions;

Контроль подлинности через Iron.defaults

Параметры Iron.defaults задают:

  • алгоритмы шифрования
  • параметры HMAC
  • формат сериализации

Изменение этих параметров требует строгого согласования между клиентом и сервером, иначе расшифровка станет невозможной.


Изоляция слоя аутентификации

Чистая архитектура предполагает, что:

  • middleware знает о токенах и Iron
  • бизнес-логика знает только о req.user
  • контроллеры не взаимодействуют с токенами напрямую

Такой подход снижает связанность компонентов и упрощает тестирование.