Гид по технологиям

Защита REST API в Node.js с помощью JWT

6 min read Безопасность Обновлено 23 Dec 2025
JWT для защиты REST API в Node.js
JWT для защиты REST API в Node.js

Ноутбук с кодом на столе и растением в кофейне

Когда вы создаёте приложение, важно защитить конфиденциальные данные от неавторизованного доступа. Современные web, мобильные и облачные приложения часто используют REST API как основной канал взаимодействия. Поэтому проектирование и разработка backend API должны учитывать безопасность с самого начала.

JSON Web Tokens (JWT) — эффективный способ обеспечить авторизацию и аутентификацию пользователей, а также защитить ресурсы от доступа злоумышленников.

Что такое JWT

JSON Web Token (JWT) — это открытый стандарт для компактной и автономной передачи информации между сторонами в виде JSON-объекта, подписанного цифровой подписью. JWT обычно используется для передачи данных о пользователе и проверки подлинности.

JWT состоит из трёх частей, разделённых точками: заголовок (header), полезная нагрузка (payload) и подпись (signature). Заголовок описывает алгоритм подписи (например, HS256), payload содержит утверждения (claims) о пользователе и метаданные, а подпись гарантирует целостность и подлинность токена.

Пример закодированного JWT слева и декодированной структуры токена справа.

Коротко: сервер подписывает payload секретом — клиент получает токен и при каждом запросе предоставляет его серверу для проверки.

Цели статьи

  • Показать простую реализацию регистрации и входа с использованием JWT в Node.js и Express.
  • Объяснить, как защищать маршруты с помощью middleware, проверяющего токен.
  • Дать практические советы по безопасности, тестированию и эксплуатации.

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

Мы построим минимальный API с регистрацией, логином и защищённым маршрутом для получения списка пользователей.

Рекомендуемые зависимости (установите в корневой папке проекта):

npm install cors dotenv bcrypt mongoose cookie-parser crypto jsonwebtoken express

Примечание: мы используем mongoose для работы с MongoDB и bcrypt для хеширования паролей.

Создайте базу данных MongoDB (локально или кластер в облаке) и в корне проекта файл .env с подключением:

CONNECTION_STRING="ваша_строка_подключения"

Настройка подключения к базе данных

Создайте файл utils/db.js и добавьте код для подключения через mongoose:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.CONNECTION_STRING, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log("Connected to MongoDB!");
  } catch (error) {
    console.error("Error connecting to MongoDB:", error);
  }
};

module.exports = connectDB;

Краткое определение: Mongoose — ODM (Object Document Mapper), упрощающий работу с MongoDB.

Модель данных пользователя

Создайте папку models и файл models/user.model.js с простым описанием пользователя:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true }
});

const User = mongoose.model("User", userSchema);
module.exports = User;

Важно: username помечен как unique — это облегчает предотвращение дублирования учётных записей.

Контроллеры: регистрация, вход и защищённый маршрут

Создайте папку controllers и файл controllers/userControllers.js. Ниже — пример реализации регистрационного и логин-контроллеров, а также обработчика защищённого маршрута.

const User = require('../models/user.model');
const bcrypt = require('bcrypt');
const { generateToken } = require('../middleware/auth');

exports.registerUser = async (req, res) => {
  const { username, password } = req.body;

  try {
    const hash = await bcrypt.hash(password, 10);
    await User.create({ username, password: hash });
    res.status(201).send({ message: 'User registered successfully' });
  } catch (error) {
    console.log(error);
    res.status(500).send({ message: 'An error occurred' });
  }
};

exports.loginUser = async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(404).send({ message: 'User not found' });
    }

    const passwordMatch = await bcrypt.compare(password, user.password);

    if (!passwordMatch) {
      return res.status(401).send({ message: 'Invalid login credentials' });
    }

    const payload = { userId: user._id };
    const token = generateToken(payload);
    res.cookie('token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production' });
    res.status(200).json({ message: 'Login successful' });
  } catch (error) {
    console.log(error);
    res.status(500).send({ message: 'An error occurred while logging in' });
  }
};

exports.getUsers = async (req, res) => {
  try {
    const users = await User.find({});
    res.json(users);
  } catch (error) {
    console.log(error);
    res.status(500).send({ message: 'An error occurred' });
  }
};

Примечание: токен возвращается в httpOnly cookie — это защищает от доступа токена через JavaScript (XSS).

Middleware аутентификации и конфигурация

Создайте папку middleware и два файла: middleware/config.js и middleware/auth.js.

middleware/config.js:

const crypto = require('crypto');

module.exports = {
  // Для реального приложения храните ключ в надёжном месте (например, в секретах облака)
  secretKey: process.env.JWT_SECRET || crypto.randomBytes(32).toString('hex')
};

Important: генерация нового секрета при каждом запуске приведёт к инвалидизации всех ранее выданных токенов. В продакшне храните JWT_SECRET явно (в переменных окружения или хранилище секретов).

middleware/auth.js:

const jwt = require('jsonwebtoken');
const { secretKey } = require('./config');

const generateToken = (payload) => {
  const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
  return token;
};

const verifyToken = (req, res, next) => {
  const token = req.cookies && req.cookies.token;

  if (!token) {
    return res.status(401).json({ message: 'No token provided' });
  }

  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      return res.status(401).json({ message: 'Invalid or expired token' });
    }

    req.userId = decoded.userId;
    next();
  });
};

module.exports = { generateToken, verifyToken };

Ключевые моменты: ограничение срока жизни токена (expiresIn) и проверка подписи помогут снизить риск использования перехваченных токенов.

Маршруты API

Создайте routes/userRoutes.js:

const express = require('express');
const router = express.Router();
const userControllers = require('../controllers/userControllers');
const { verifyToken } = require('../middleware/auth');

router.post('/api/register', userControllers.registerUser);
router.post('/api/login', userControllers.loginUser);
router.get('/api/users', verifyToken, userControllers.getUsers);

module.exports = router;

Точка входа сервера

Пример server.js:

const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 5000;
require('dotenv').config();
const connectDB = require('./utils/db');
const cookieParser = require('cookie-parser');

connectDB();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({ credentials: true, origin: true }));
app.use(cookieParser());
const userRoutes = require('./routes/userRoutes');
app.use('/', userRoutes);

app.listen(port, () => {
  console.log(`Server is listening at http://localhost:${port}`);
});

Запуск сервера в разработке:

node server.js

Тестирование: используйте Postman, curl или интеграционные тесты для запросов к /api/register, /api/login и /api/users. При успешном логине cookie с токеном автоматически отправляется клиенту и включается в последующие запросы.

Практические рекомендации по безопасности

JWT — мощный инструмент, но он не закрывает все проблемы. Ниже — набор практик для повышения безопасности API.

  • Используйте HTTPS повсеместно. Токены и учётные данные должны передаваться только по TLS.
  • Храните секреты (JWT_SECRET) вне кода: переменные окружения, секретное хранилище в облаке.
  • Устанавливайте короткий срок жизни токена и используйте механизм обновления (refresh tokens) с безопасным хранением и возможностью отзыва.
  • Ограничивайте доступ по ролям: добавляйте в payload роль или список прав и проверяйте их в middleware.
  • Проводите валидацию и санитизацию входных данных (например, express-validator).
  • Ограничивайте скорость запросов (rate limiting) для защиты от брутфорса.
  • Логируйте события безопасности (входы, неудачные попытки) и отслеживайте аномалии.
  • При хранении токена в cookie: ставьте httpOnly, secure и SameSite=strict/ lax в зависимости от архитектуры.

Когда JWT не лучшее решение (контрпримеры)

  • Если нужно мгновенно отзывать токены у большого числа пользователей: стандартный JWT без сессий и централизованного черного списка сложнее отзывать. Решение: храните список отозванных идентификаторов токенов или используйте короткий срок жизни + refresh tokens.
  • Если вы не контролируете домен клиента (встраиваемый виджет), хранение токенов в localStorage уязвимо к XSS.
  • Для очень чувствительных операций (финансы) имеет смысл применять двуфакторную аутентификацию и дополнительные проверки.

Альтернативы и гибридные подходы

  • Серверные сессии (session ID в cookie) — проще отзывать и контролировать, но требует серверного хранилища сессий.
  • OAuth2 + OpenID Connect — стандарт для авторизации через сторонние провайдеры и делегирования прав.
  • Комбинация JWT (access token) и refresh token — распространённый баланс между удобством и безопасностью.

Модель принятия решений (когда что выбрать)

  • Нужна масштабируемая stateless аутентификация → JWT с коротким сроком жизни + refresh tokens.
  • Требуется быстрый отзыв доступа и контроль сессий → серверные сессии или централизованный blacklist для JWT.
  • Нужно делегировать аутентификацию внешнему провайдеру → OAuth2/OIDC.

Чек-лист ролей (разработчик / DevOps / SRE)

  • Разработчик:

    • Хеширование паролей (bcrypt) — выполнено.
    • Валидация входных данных на всех маршрутах.
    • Ограничение прав на маршрутах по ролям.
  • DevOps:

    • Хранение JWT_SECRET в безопасном хранилище.
    • TLS для всех внешних подключений.
    • Настройка резервирования и мониторинга БД.
  • SRE / Безопасность:

    • Настройка rate limiting и WAF.
    • Политики логирования и оповещений по подозрительным событиям.
    • План отзыва ключей и ротации секретов.

Тесты и критерии приёмки

  • Регистрация: POST /api/register создаёт пользователя с захешированным паролем.
  • Вход: POST /api/login возвращает httpOnly cookie с валидным JWT.
  • Защищённый маршрут: GET /api/users возвращает 401 без токена, 200 с валидным токеном.
  • Тесты на истечение срока действия токена и некорректную подпись.

Пример сценария инцидента и откат

  1. Выявлено, что секретный ключ JWT скомпрометирован.
  2. Откат: немедленно сменить JWT_SECRET, увеличить логирование неудачных попыток.
  3. Инвалидировать токены: либо централизованный blacklist, либо заставить пользователей повторно пройти аутентификацию (например, уменьшить срок жизни токенов и не выдавать refresh tokens).
  4. Проанализировать ошибку и восстановить процессы выпуска ключей.

Краткая сводка мер безопасности (матрица рисков)

  • Утечка токена через XSS — риск: высокий; смягчение: httpOnly cookie, CSP, защита от XSS.
  • Перехват токена в сети — риск: высокий; смягчение: HTTPS.
  • Кража секретного ключа — риск: критичный; смягчение: хранение секретов в Vault, ротация ключей, мониторинг.

Локальные и практические советы для развёртывания

  • В продакшне не генерируйте секрет ключ на старте — используйте управляемые секреты.
  • Если фронтенд и бэкенд на разных доменах, настройте CORS с credentials:true и выставьте SameSite для cookie корректно.
  • Для мобильных клиентов рассмотрите безопасное хранилище токенов (Keychain/Keystore).

Глоссарий (в 1 строку)

  • JWT — подписьованный JSON-токен для передачи утверждений о пользователе.
  • Access token — токен с коротким сроком жизни для доступа к ресурсам.
  • Refresh token — более долгоживущий токен для получения новых access token.

Итог

JWT упрощают реализацию stateless аутентификации и хорошо подходят для масштабируемых REST API. Однако сами по себе они не решают всех задач безопасности — необходим комплексный подход: HTTPS, хранение секретов, валидация входа, ограничение прав, мониторинг и возможность отзыва токенов. Следуя описанным в статье шаблонам, вы получите рабочую и более безопасную отправную точку для авторизации в Node.js приложении.

Важно: прежде чем использовать JWT в продакшне, оцените требования к отзыву сессий и хранению секретов — это ключевые архитектурные решения.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как настроить домашний медиасервер
Hardware

Как настроить домашний медиасервер

Установка macOS на ПК — подробный гид
Руководство

Установка macOS на ПК — подробный гид

Открыть Локальные пользователи и группы — Windows 11
Windows

Открыть Локальные пользователи и группы — Windows 11

Продвинутый поиск в LinkedIn — тактики и шаблоны
Карьера

Продвинутый поиск в LinkedIn — тактики и шаблоны

Как очистить корзину на Android
Android.

Как очистить корзину на Android

Профили в Amazon Prime Video: настройка и управление
Streaming

Профили в Amazon Prime Video: настройка и управление