Как защитить бэк от двойного запроса с фронта?

Двойной запрос с фронта - это ситуация, когда пользователь отправляет два или более одинаковых запроса на сервер. Это может произойти по разным причинам, таким как повторное нажатие кнопки или проблемы с сетью. Двойные запросы могут привести к нежелательным последствиям, таким как создание дубликатов данных или некорректное выполнение операций.

Чтобы защитить бэкенд от двойных запросов с фронта, можно использовать несколько подходов:

1. Отключение кнопки после первого нажатия: При первом нажатии на кнопку на стороне клиента, следует отключить ее для предотвращения повторного нажатия. Это можно сделать с помощью JavaScript, изменяя свойство disabled кнопки. Таким образом, пользователь не сможет выполнить повторный запрос, пока не будет разрешено.

Пример:

const button = document.querySelector('#myButton');
button.addEventListener('click', () => {
  // Запрещаем повторные нажатия кнопки
  button.disabled = true;

  // Выполняем запрос к серверу
  fetch('/api/myEndpoint')
    .then(response => response.json())
    .then(data => {
      // Обрабатываем полученные данные

      // Разрешаем повторное нажатие кнопки
      button.disabled = false;
    })
    .catch(err => {
      // Обрабатываем ошибку

      // Разрешаем повторное нажатие кнопки
      button.disabled = false;
    });
});

2. Уникальный токен запроса: При каждом запросе с фронтенда можно включать уникальный токен запроса (например, CSRF-токен) в заголовок или тело запроса. Сервер может проверить этот токен и разрешить выполнение только одного запроса с данным токеном. Если сервер получает несколько запросов с одним и тем же токеном, он может игнорировать все последующие запросы или возвращать ошибку.

Пример использования CSRF-токена с Express и пакетом csurf:

const express = require('express');
const csrf = require('csurf');
const app = express();

// Инициализация csurf
const csrfProtection = csrf({ cookie: true });

// Маршрут, требующий проверку CSRF-токена
app.post('/api/myEndpoint', csrfProtection, (req, res) => {
  // Обрабатываем запрос
});

// Генерация страницы с CSRF-токеном
app.get('/myPage', csrfProtection, (req, res) => {
  res.render('myPage', { csrfToken: req.csrfToken() });
});

3. Идемпотентные операции: Если запросы к серверу можно сделать идемпотентными - то есть обеспечить одинаковый результат, несмотря на множественное выполнение - то двойные запросы не будут вызывать проблем. Например, если запрос на обновление данных в базе данных не зависит от текущего состояния данных, то его можно безопасно выполнить несколько раз.

4. Журнал запросов: Вести журнал всех запросов, отправленных с фронтенда, и проверять каждый запрос на уникальность перед выполнением. Если запрос повторяется, сервер может игнорировать его или возвращать ошибку.

5. Ограничение частоты запросов: Реализовать ограничение частоты запросов для каждого пользователя. Например, можно использовать пакет rate-limit для ограничения числа запросов, которые пользователь может сделать за определенный период времени.

Пример использования пакета rate-limit с Express:

const express = require('express');
const rateLimit = require('express-rate-limit');
const app = express();

// Ограничение до 100 запросов в течение 1 часа для каждого IP-адреса
const limiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 час
  max: 100, // максимальное число запросов
});

// Применение ограничения ко всем запросам
app.use(limiter);

// Маршрут, который требует проверку ограничения
app.post('/api/myEndpoint', (req, res) => {
  // Обрабатываем запрос
});

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