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

Защита GraphQL API с помощью JWT

8 min read Безопасность API Обновлено 30 Dec 2025
Защита GraphQL API с JWT
Защита GraphQL API с JWT

Слово «API» окружено абстрактными иконками

GraphQL — альтернатива классическим REST API, позволяющая клиентам гибко запрашивать только нужные поля. Эта гибкость усиливает удобство, но одновременно увеличивает поверхность для атак, особенно в частях, касающихся контроля доступа. Одним из распространённых и практичных способов обеспечить аутентификацию и авторизацию является использование JSON Web Tokens (JWT).

Важно: JWT — это формат токена для передачи утверждений (claims). Он сам по себе не делает приложение безопасным — это инструмент, который нужно правильно внедрить.

Что вы получите из этого руководства

  • Как настроить Express.js + Apollo Server для GraphQL.
  • Как хранить пользователей в MongoDB через Mongoose.
  • Как генерировать и верифицировать JWT, интегрируя их в контекст резолверов.
  • Лучшие практики безопасности: хеширование паролей, срок действия токенов, отзыв токенов, защита от перегрузки запросами.
  • Тесты, критерии приёмки и чек-листы для ролей.

Основные термины в 1 строке

  • JWT: компактный токен для передачи утверждений между сторонами.
  • Резолвер: функция, возвращающая данные для поля схемы GraphQL.
  • Контекст (context): объект, доступный всем резолверам для хранения информации запроса (заголовки, пользователь и т. п.).

Аутентификация и авторизация в GraphQL API

В отличие от REST, GraphQL обычно имеет одну точку входа. Клиенты могут запрашивать переменное количество данных — это удобно, но усложняет разграничение доступа. Типичные риски: отсутствие проверки прав на уровне полей, «over-fetching» чувствительной информации и broken access control.

Экран ноутбука с кодом на экране и держатель ручек рядом

Рекомендуется сочетать аутентификацию (кто вы) и авторизацию (что вам разрешено). JWT обычно используются для аутентификации; авторизация — логика в резолверах, посредниках или на уровне схемы.

Важно: всегда храните токен и секрет аккуратно и не сохраняйте пароли в виде открытого текста (см. раздел «Безопасность» ниже).

Источник кода проекта доступен в репозитории проекта (укажите свой репозиторий при публикации).

Установка Express.js + Apollo Server

Apollo Server — популярная реализация GraphQL-сервера. Он помогает быстро определить схемы, резолверы и подключить источники данных.

Создайте папку проекта и инициализируйте npm:

mkdir graphql-API-jwt
cd graphql-API-jwt
npm init --yes

Установите зависимости:

npm install apollo-server graphql mongoose jsonwebtoken dotenv express

Примечание: в оригинале был пакет “jsonwebtokens” — правильное имя пакета для JWT в Node.js — “jsonwebtoken”.

Создайте файл server.js в корне и настройте сервер:

const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();

const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({ req }),
});

const MONGO_URI = process.env.MONGO_URI;

mongoose
  .connect(MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to DB");
    return server.listen({ port: 5000 });
  })
  .then((res) => {
    console.log(`Server running at ${res.url}`);
  })
  .catch(err => {
    console.log(err.message);
  });

Контекст сервера прокидывает объект req в резолверы, что позволяет проверять заголовки запросов и извлекать токены.

Подключение MongoDB

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

MONGO_URI=""

И храните секрет для подписи JWT тоже в .env:

SECRET_KEY=""

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

Создайте models/user.js:

const { model, Schema } = require('mongoose');

const userSchema = new Schema({
  name: String,
  password: String,
  role: String
});

module.exports = model('user', userSchema);

Важно: в продакшне никогда не храните пароли в открытом виде. Всегда используйте устойчивое хеширование (bcrypt, Argon2) и соль.

Схема GraphQL

В папке graphql создайте typeDefs.js:

const { gql } = require("apollo-server");

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    password: String!
    role: String!
  }
  input UserInput {
    name: String!
    password: String!
    role: String!
  }
  type TokenResult {
    message: String
    token: String
  }
  type Query {
    users: [User]
  }
  type Mutation {
    register(userInput: UserInput): User
    login(name: String!, password: String!): TokenResult
  }
`;

module.exports = typeDefs;

Комментарий: поле password обычно не возвращается клиенту. В схеме выше оно присутствует для совместимости с примером, но в реальном API не включайте пароль в type User.

Резолверы и JWT: генерация и проверка

Добавьте в graphql/resolvers.js логику для генерации/проверки JWT и сами резолверы.

const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;

function generateToken(user) {
  const token = jwt.sign(
    { id: user.id, role: user.role },
    secretKey,
    { expiresIn: '1h', algorithm: 'HS256' }
  );

  return token;
}

function verifyToken(token) {
  if (!token) {
    throw new Error('Token not provided');
  }

  try {
    const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
    return decoded;
  } catch (err) {
    throw new Error('Invalid token');
  }
}

const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('Name, password, and role required');
      }

      const newUser = new User({
        name: name,
        password: password,
        role: role,
      });

      try {
        const response = await newUser.save();

        return {
          id: response._id,
          ...response._doc,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Failed to create user');
      }
    },
    login: async (_, { name, password }) => {
      try {
        const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('User not found');
        }

        if (password !== user.password) {
          throw new Error('Incorrect password');
        }

        const token = generateToken(user);

        if (!token) {
          throw new Error('Failed to generate token');
        }

        return {
          message: 'Login successful',
          token: token,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Login failed');
      }
    }
  },
  Query: {
    users: async (parent, args, context) => {
      try {
        const token = context.req.headers.authorization || '';
        const decodedToken = verifyToken(token);

        if (decodedToken.role !== 'Admin') {
          throw new Error('Unauthorized. Only Admins can access this data.');
        }

        const users = await User.find({}, { name: 1, _id: 1, role: 1 });
        return users;
      } catch (error) {
        console.error(error);
        throw new Error('Failed to fetch users');
      }
    },
  },
};

module.exports = resolvers;

Важные замечания к примеру выше:

  • Пароли сравниваются напрямую — это уязвимо. Внедрите bcrypt: храните только хеш и сравнивайте через bcrypt.compare.
  • Токен берётся из заголовка Authorization. Часто используется формат “Bearer ” — удаляйте префикс перед верификацией.
  • Для отказа от токена (ревока) можно использовать список отозванных токенов (черный список) или хранить у пользователя токен-идентификатор.

Запуск сервера и тестирование

Запустите сервер:

node server.js

Откройте Apollo Server sandbox (или GraphQL Playground / GraphiQL) по указанному адресу. Протестируйте:

  • mutation register — создать пользователя.
  • mutation login — получить токен.
  • Добавьте токен в поле авторизации: часто это заголовок Authorization: Bearer .
  • Сделайте query users — убедитесь, что доступ есть только у Admin.

Пример GraphQL мутации Login в Apollo sandbox

Пример GraphQL запроса в Apollo sandbox

Безопасность: рекомендации и харднинг

Important: базовые механизмы аутентификации и авторизации — необходимы, но недостаточны. Ниже — набор практических мер.

  • Хешируйте пароли: используйте bcrypt или Argon2. Никогда не храните пароли в открытом виде.
  • Используйте HTTPS: передача токенов и паролей по незашифрованному каналу недопустима.
  • Устанавливайте разумный срок жизни токена (например, 1 час) и используйте Refresh-токены, хранящиеся безопасно.
  • Защита от CSRF: если храните токен в cookie, обеспечьте защиту CSRF-токеном.
  • Ограничение глубины и сложности запросов: внедрите лимит глубины (query depth limit) и лимит на общую стоимость запроса (query cost analysis).
  • Rate limiting: ограничивайте количество запросов с одного IP/пользователя.
  • Логирование и мониторинг: фиксируйте неудачные попытки аутентификации и аномалии.
  • Validation: валидируйте ввод на уровне резолверов и/или схемы; проверяйте типы и допустимые значения.
  • Минимизация данных: отдавайте минимально необходимую информацию — не включайте пароли и прочие секреты в ответы.
  • Ревок токенов: для возможности отзыва токенов держите версию токена в профиле пользователя (tokenVersion) или чёрный список.
  • Хранение секретов: используйте менеджер секретов (Vault, AWS Secrets Manager) или управляемые переменные окружения.

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

  • Session-based authentication (сессии на сервере) — проще реализовать отзыв токенов, но сложнее масштабировать без централизованного хранилища сессий.
  • OAuth2 / OpenID Connect — для федеративной аутентификации и SSO.
  • API Gateways / прокси — вынесение аутентификации/авторизации в слой API Gateway для унификации и защиты точек входа.

Когда JWT не подходит (контрпримеры)

  • Когда требуется мгновенный отзыв токенов для большого количества пользователей и нет инфраструктуры для чёрного списка.
  • Когда политика безопасности требует хранить сессии исключительно на сервере.
  • Для короткоживущих одноразовых операций, где проще использовать временные одноразовые коды.

Мини‑методология внедрения JWT (шаги)

  1. Проектирование: решите, какие данные попадут в payload token (id, role, tokenVersion).
  2. Секреты: выберите алгоритм (HS256/RS256). Для RS256 нужен парный приватный/публичный ключ.
  3. Хеширование паролей: мигрируйте существующие пароли на безопасный хеш.
  4. Контекст: прокиньте req в context каждого резолвера.
  5. Валидация: добавьте проверку формата и лимитов запросов.
  6. Тесты: покройте сценарии логина, доступа, отказа и ревока.
  7. Мониторинг и аудит: логируйте подозрительные события.

Чек-листы по ролям

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

  • Реализовал генерацию и верификацию JWT.
  • Использует хеширование паролей.
  • Не возвращает пароль в ответах.
  • Обрабатывает формат “Bearer ”.

Security Engineer / DevSecOps:

  • Настроил хранение SECRET_KEY в безопасном хранилище.
  • Включил HTTPS и проверил конфигурацию TLS.
  • Добавил лимиты глубины и стоимости запросов.
  • Настроил ревок токенов/черный список.

Ops / SRE:

  • Наблюдает за метриками аутентификации (ошибки, латентность).
  • Настроил алерты на всплески неудачных попыток логина.
  • Обеспечил безопасное резервное копирование БД.

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

Критерии приёмки:

  • Регистрация создаёт пользователя в базе с хешированным паролем.
  • Логин с корректными учётными данными возвращает JWT.
  • Запрос users без токена — 401/ошибка “Token not provided”.
  • Запрос users с токеном не-Admin — возвращается ошибка авторизации.
  • Запрос users с токеном Admin — возвращает список пользователей без паролей.

Тестовые сценарии (автоматизируемые):

  • TC-01: Успешная регистрация нового пользователя.
  • TC-02: Попытка регистрации с пустыми полями — 400/ошибка.
  • TC-03: Успешный логин и получение токена.
  • TC-04: Использование просроченного токена — ошибка “Invalid token”.
  • TC-05: Попытка доступа к users с ролью User — отказ.

Миграция и совместимость

При миграции существующих систем:

  • Добавьте флаг миграции для старых пользователей, чтобы принудить смену пароля и хеширование.
  • Внедрите постепенное требование refresh-токенов, если ранее использовались долгоживущие токены.

Конфиденциальность и соответствие (GDPR)

  • Минимизируйте персональные данные, возвращая только необходимые поля.
  • Обеспечьте возможность удаления/анонимизации данных по запросу пользователя.
  • Логируйте доступ к чувствительным данным и храните логи в соответствии с политикой хранения.

Короткий план реагирования при инциденте с токенами

  1. Отключить службу аутентификации или временно увеличить контроль доступа.
  2. Ревокнуть актуальные токены (увеличить tokenVersion или занести в черный список).
  3. Проинформировать пользователей, при необходимости инициировать принудительную смену паролей.
  4. Провести аудит логов и исправить уязвимость.
  5. Выпустить обновление и инструкцию для пользователей.

Итог и рекомендации

Аутентификация и авторизация с помощью JWT — удобный и распространённый подход для GraphQL API, особенно в сочетании с Apollo Server. Однако безопасность — это многослойная задача: токены должны корректно генерироваться и валидироваться, пароли — хешироваться, а инфраструктура — защищаться от злоупотреблений (rate limiting, depth limiting, HTTPS и т. п.).

Рекомендации по приоритету внедрения:

  1. Хеширование паролей (срочно).
  2. Введение HTTPS и безопасного хранения секретов.
  3. Настройка срока жизни токенов и стратегия ревока.
  4. Лимиты запросов и защита от глубоких/дорогих запросов.

Короткая заметка: начните с простого рабочего прототипа, затем постепенно внедряйте дополнительные слои защиты и покрывайте всё автоматизированными тестами.

Summary:

  • JWT подходят для аутентификации, но требуют дополнительных мер (ревок, харднинг).
  • Не храните пароли в открытом виде; используйте bcrypt/Argon2.
  • Контекст GraphQL удобен для передачи информации о запросе в резолверы.
  • Тесты и мониторинг — критичные элементы надёжности.

Important: перед выводом в продакшн пройдите независимый аудит безопасности и нагрузочное тестирование.

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство