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

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

7 min read Безопасность API Обновлено 08 Jan 2026
Защита GraphQL API с JWT в Express и Apollo
Защита GraphQL API с JWT в Express и Apollo

Введение

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

GraphQL — гибкая альтернатива традиционным REST API. Она позволяет клиенту запрашивать ровно те поля, которые нужны. Эта гибкость упрощает разработку, но одновременно меняет модель рисков: вместо множества конечных точек у вас один эндпоинт, который должен реализовывать тонкую и надёжную проверку прав доступа.

JSON Web Tokens (JWT) — распространённый механизм для передачи утверждений об аутентификации и авторизации между клиентом и сервером. В статье показано, как интегрировать JWT в стек Express + Apollo Server, как проверять роли (например, Admin) и какие дополнительные меры безопасности стоит применять.

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

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

В GraphQL обычно один эндпоинт, поэтому доступ к разным данным должен отличаться в зависимости от пользователя и его ролей. Неправильная проверка прав приводит к уязвимостям с нарушением контроля доступа. Лучше реализовать центральную проверку токенов в контексте запроса и затем использовать роль или другие утверждения из токена для фильтрации данных.

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

Рекомендация: проверяйте права не только на уровне Query/Mutation, но и на уровне полей, если поля возвращают чувствительную информацию.

Вы можете найти код проекта в репозитории автора (ссылка должна быть добавлена разработчиком проекта).

Быстрая установка проекта (Express + Apollo)

  1. Создайте папку проекта и перейдите в неё:
mkdir graphql-API-jwt  
cd graphql-API-jwt
  1. Инициализируйте npm:
npm init --yes
  1. Установите зависимости (обратите внимание на корректные имена пакетов):
npm install apollo-server graphql mongoose jsonwebtoken dotenv

Примечание: в примерах часто встречается пакет jsonwebtoken. Убедитесь, что в package.json указан он, а не ошибочные варианты.

  1. Создайте файл 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);  
  });

Пояснение: опция context передаёт объект запроса в резолверы. Там мы будем читать заголовок Authorization и проверять JWT.

Настройка MongoDB

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

MONGO_URI=""  
SECRET_KEY=''

Храните секретный ключ в безопасном хранилище (Vault, AWS KMS, Azure Key Vault). Не коммитите .env в git.

Определение модели данных (Mongoose)

Создайте файл 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 для хеширования паролей и сопутствующие меры (соль, work factor).

Определение схемы GraphQL

Создайте папку graphql и файлы typeDefs.js и resolvers.js.

В файле 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!, role: String!): TokenResult  
  }  
`;  
  
module.exports = typeDefs;

Примечание: сигнатура login в typeDefs включает role. В примере резолвера роль может передаваться либо при логине, либо определяться сервером. Согласуйте сигнатуру API и реализацию. Если роль не нужна при логине, удалите её из typeDefs.

Реализация резолверов и JWT

Создайте graphql/resolvers.js. В начале файла — импорт моделей и библиотек:

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;  
}

Комментарий: в примере срок жизни “1h” (1 час). Для длинных сессий рассмотрите использование refresh token и коротко живущего access 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;

Важно: код выше демонстрационный. Обязательно замените хранение паролей на хеши и рассмотрите использование refresh-token механизма.

Как тестировать

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

node server.js

Откройте UI Apollo Server (песочница) в браузере. Примеры мутации и запроса можно выполнять там.

Пример мутации Login в песочнице Apollo Server

Добавьте токен в заголовок Authorization следующим образом:

Authorization: Bearer <ваш_JWT>

Пример запроса GraphQL в песочнице Apollo Server

Безопасное использование JWT — чек-лист

  • Используйте короткоживущие access-токены (например, несколько минут — час). Долгие токены повышают риск компрометации.
  • Храните refresh-токены отдельно и в безопасном хранилище (HttpOnly куки или защищённое хранилище на сервере).
  • Подписывайте токены надёжным секретом или асимметричными ключами (RS256) при большом масштабе.
  • Реализуйте механизм отзыва токенов (blacklist) для экстренных случаев.
  • Включите проверку алгоритма при верификации токена.
  • Передавайте токен в заголовке Authorization как Bearer.
  • Используйте HTTPS на всех уровнях.
  • Валидируйте входные данные (input validation) и ограничивайте глубину запросов (query depth) и вычислительную сложность.
  • Скрывайте подробные ошибки в продакшене (masking errors).
  • Логируйте попытки неудачной авторизации и аномалии.

Когда JWT не подходит или имеет ограничения

  • Нужна мгновенная отзывка большого количества токенов — JWT без централизованного хранилища отзывов неудобен.
  • Приложение требует полной серверной сессии с хранением состояния (в этом случае лучше серверные сессии).
  • Требуется высокий уровень защиты ключей без возможности ротации — применяйте асимметричную подпись и централизованное управление ключами.

Альтернативы: OAuth 2.0 (для делегированной авторизации), OpenID Connect (аутентификация), серверные сессии (stateful auth).

Ментальные модели и эвристики

  • “Минимальные права” — всегда давайте ровно те роли и права, которые нужны для конкретного действия.
  • “Беспокоиться о нарушениях на ранней стадии” — предполагайте, что токен может быть скомпрометирован, и постройте защиту с учётом этого.
  • “Делегируй аутентификацию, контролируй авторизацию” — внешние провайдеры (Auth0, Cognito) удобны для аутентификации; бизнес-логику по правам оставляйте в приложении.

Ролевые чек-листы

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

  • Реализовать хеширование паролей.
  • Добавить ограничения по сложности паролей.
  • Убедиться в корректной обработке ошибок.

DevOps / Инженер платформы:

  • Хранение секретов в KMS/Vault.
  • Настроить HTTPS и HSTS.
  • Регулярно ротировать секреты.

Команда безопасности:

  • Провести тесты на контроль доступа (privilege escalation).
  • Провести SAST/DAST сканирование.
  • Настроить мониторинг и оповещения на подозрительную активность.

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

  1. Прототип: реализовать basic login/register с генерацией JWT.
  2. Тесты: добавить юнит- и интеграционные тесты для логина и access-контроля.
  3. Безопасность: заменить открытые пароли на хеши, настроить хранение секретов.
  4. Отзыв: реализовать таблицу revoked tokens или версионирование ключей.
  5. Нагрузочное тестирование и лимитирование запросов.
  6. Релиз и мониторинг.

Decision tree для выбора метода аутентификации

flowchart TD
  A[Требуется аутентификация?] -->|Нет| B[Нет защиты]
  A -->|Да| C{Требуется делегирование}
  C -->|Да| D[OAuth2 / OIDC]
  C -->|Нет| E{Нужно ли хранить состояние сессии}
  E -->|Да| F[Серверные сессии]
  E -->|Нет| G[JWT с access/refresh]
  G --> H{Нужна мгновенная отзывка}
  H -->|Да| I[JWT + централизованная таблица отзывов]
  H -->|Нет| J[Стандартный JWT]

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

  • Регистрация создаёт пользователя с хешированным паролем.
  • Логин возвращает валидный JWT со сроком действия и ролью в payload.
  • Запрос users возвращает данные только при наличии корректного токена Admin.
  • Попытка получить users без токена или с токеном не‑Admin даёт ошибку 403/Unauthorized.

Тест-кейсы и acceptance

  1. Регистрация: передать валидные данные — пользователь создан (200/ок).
  2. Логин: верный пароль — получен токен; неверный — ошибка.
  3. Доступ к users с Admin токеном — данные возвращены.
  4. Доступ к users с токеном не‑Admin — отказ.

Инцидентный план: компрометация секретного ключа

  1. Блокируйте ingress в минимально необходимом объёме (если нужно).
  2. Ротация секретов: выпустите новый ключ и опубликуйте короткое время жизни для предыдущих токенов.
  3. Включите принудительное invalidation — добавьте текущие токены в блоклист.
  4. Уведомите пользователей и примите меры по расследованию.
  5. Восстановите нормальную работу, проверив логи и обновив контроль доступа.

Пример заголовка Authorization

Authorization: Bearer 

Glossary — 1 строка

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

Приватность и соответствие требованиям (GDPR)

  • Минимизируйте объём персональных данных в токене.
  • Не включайте в токен чувствительную информацию (пароли, PII) в явном виде.
  • Реализуйте процессы удаления данных по требованию (right to be forgotten): удаление из БД должно учитывать зависимые сущности и кеши.

Советы по hardening и дополнительные механики

  • Ограничьте размер и глубину запросов (depth limiting).
  • Используйте persisted queries для снижения рискованных динамических запросов.
  • Включите rate limiting и WAF на уровне API-шлюза.
  • Подключите мониторинг SLI/SLO: время ответа, ошибки 4xx/5xx, частота отвергнутых авторизаций.

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

  • jwt библиотеки есть для большинства языков — Node, Java, Go, Python, Ruby.
  • При переходе с stateful на stateless auth: планируйте миграцию с поддержкой обоих режимов и механизмом ревокации.

Короткое резюме

Мы разобрали, как добавить JWT в GraphQL API на базе Apollo Server и MongoDB. Показаны примеры кода, исправлены распространённые ошибки и описаны практики безопасности: хранение секретов, ротация ключей, отзыв токенов, защита от чрезмерно сложных запросов и рекомендации по тестированию.

Ключевые выводы:

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

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

Поделиться: 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 — руководство