CSRF у Next.js – як ефективно захистити застосунок за допомогою SameSite і double‑submit token

Практичний посібник з захисту застосунку Next.js від атак CSRF – SameSite cookies і метод double‑submit token в одному рішенні.

CSRF w Next.js – jak skutecznie chronić aplikację przy użyciu SameSite i double‑submit token

Cross‑Site Request Forgery (CSRF) залишається одним із найчастіше використовуваних векторів атаки у веб‑додатках. Хоча такі фреймворки, як Next.js, пропонують безліч механізмів, що спрощують створення безпечних інтерфейсів, розробники часто ігнорують базові засоби захисту. У цій статті ми крок за кроком покажемо, як захистити застосунок Next.js від CSRF, поєднавши налаштування SameSite cookies та шаблон double‑submit token. Розглянемо модель загрози, механіку роботи, приклади коду, чек‑ліст мітигації та типові підводні камені.

Модель загрози – як виглядає атака CSRF

Атака CSRF полягає у використанні авторизованого контексту браузера жертви. Користувач залогінений у застосунку, а потім відвідує шкідливу сторінку, яка надсилає HTTP‑запит до захищеного endpoint (наприклад, POST /api/transfer). Браузер автоматично додає сесійні куки, завдяки чому сервер вважає запит автентичним. Без додаткового механізму верифікації сервер не розрізняє такий запит від легітимної дії користувача.

Чому SameSite cookies не достатньо самостійно

Встановлення прапорця SameSite=Lax або SameSite=Strict у сесійному куці обмежує його автоматичну передачу в контексті cross‑site. Однак на практиці існують сценарії, коли браузери дозволяють передачу куки (наприклад, GET‑запити у режимі Lax або старі браузери без підтримки). Тому рекомендується поєднувати SameSite з додатковим токеном, який перевіряється на боці сервера.

Double‑submit token – принцип роботи

Шаблон double‑submit token передбачає генерацію випадкового токена на боці сервера і передачу його одночасно в куці та в заголовку (або в тілі) запиту. Сервер при отриманні запиту порівнює два значення – якщо вони збігаються, запит вважається автентичним. Токен не потребує зберігання на сервері (stateless), що спрощує масштабування.

«Подвійний токен – найпростіший спосіб впровадити сильну верифікацію CSRF без створення сесії на сервері.»

Реалізація у Next.js – серверна частина

Next.js надає API Routes, які працюють як серверні функції. Нижче наведено фрагмент коду, який генерує токен, записує його в куку SameSite=Lax та верифікує при наступних запитах.

import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';

const CSRF_COOKIE = 'csrfToken';
const CSRF_HEADER = 'x-csrf-token';

function generateToken() {
  return crypto.randomBytes(32).toString('hex');
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // 1️⃣ Генерація токена при GET (наприклад, при рендері форми)
  if (req.method === 'GET') {
    const token = generateToken();
    res.setHeader('Set-Cookie', `${CSRF_COOKIE}=${token}; HttpOnly; SameSite=Lax; Path=/; Secure`);
    return res.status(200).json({ csrfToken: token });
  }

  // 2️⃣ Верифікація при POST/PUT/DELETE
  const cookieToken = req.cookies[CSRF_COOKIE];
  const headerToken = req.headers[CSRF_HEADER];

  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }

  // … подальша логіка endpoint'у
  return res.status(200).json({ success: true });
}

У наведеному прикладі токен генерується при першому GET і повертається одночасно в куці та у відповіді JSON. Клієнт (наприклад, React‑компонент) повинен зчитати токен і додати його в заголовок x-csrf-token при подальших мутуючих запитах.

Реалізація у Next.js – клієнтська частина (React)

У React‑компонентах можна використати хук useEffect для отримання токена і встановлення дефолтного заголовка у бібліотеці axios або fetch. Нижче приклад з fetch:

import { useEffect } from 'react';

export default function useCsrf() {
  useEffect(() => {
    fetch('/api/csrf')
      .then(r => r.json())
      .then(data => {
        // токен доступний у data.csrfToken та у cookie
        window.csrfToken = data.csrfToken;
      });
  }, []);
}

export async function csrfFetch(url, options = {}) {
  const headers = {
    ...options.headers,
    'x-csrf-token': window.csrfToken,
  };
  return fetch(url, { ...options, headers, credentials: 'include' });
}

Встановлення credentials: 'include' забезпечує, що кука з токеном буде додана до запиту, а заголовок x-csrf-token дозволяє подвійну верифікацію.

Чек‑лісти мітигації CSRF у Next.js

  • Встановіть прапорець SameSite=Lax (або Strict, якщо застосунок не потребує cross‑site POST) та Secure у кожному сесійному куці.
  • Генеруйте унікальний токен per session і зберігайте його у http‑only cookie.
  • Надсилайте токен у спеціальному заголовку (x-csrf-token) при усіх мутуючих запитах.
  • Перевіряйте відповідність токену з cookie і заголовка на боці сервера перед виконанням бізнес‑логіки.
  • Обмежте час життя токену (наприклад, 30 хв) і оновлюйте його при кожній новій сесії.
  • Переконайтеся, що всі API‑endpoint'и, що приймають дані (POST, PUT, DELETE, PATCH), мають увімкнену верифікацію CSRF.

Типові помилки та компроміси

Розробники часто допускають такі помилки:

  • Відсутність прапорця SameSite – у старих браузерах токен може бути використаний, що послаблює захист.
  • Використання куки без httpOnly – дозволяє шкідливому скрипту прочитати токен, усуваючи перевагу подвійного токена.
  • Зберігання токена у localStorage – піддає його крадіжці через XSS.
  • Перевірка токена лише на клієнті – не забезпечує реального захисту, оскільки атакуючий може обійти JavaScript‑код.
  • Встановлення SameSite=Strict у застосунку, який потребує зовнішніх посилань – може призвести до відхилення легітимних запитів.

На практиці найнадійніше комбінувати SameSite з double‑submit token, одночасно застосовуючи принцип least‑privilege – мінімізувати права куків і токенів, обмежуючи їх область до необхідних шляхів (Path=/api).

Посилання на стандарти та кращі практики

Описані техніки відповідають рекомендаціям OWASP Top 10 – A05:2021 – Security Misconfiguration та CWE‑352 (Cross‑Site Request Forgery). Додатково, використання прапорця SameSite та принципу least‑privilege рекомендовано у OWASP Secure Headers Project. Реалізація double‑submit token задовольняє вимогу OWASP ASVS 2.2.1 (CSRF Prevention).

Практичний приклад – повний код API та хук React

Нижче повний, готовий до копіювання приклад, який можна розмістити у /pages/api/csrf.ts та у власному хуці /hooks/useCsrf.ts. Завдяки цьому ви одразу отримуєте endpoint, що генерує токен, і просту функцію для виконання захищених запитів.

// /pages/api/csrf.ts
import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';

const COOKIE_NAME = 'csrfToken';
const HEADER_NAME = 'x-csrf-token';

function newToken() {
  return crypto.randomBytes(24).toString('base64');
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    const token = newToken();
    res.setHeader('Set-Cookie', `${COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Secure; Path=/; Max-Age=1800`);
    return res.status(200).json({ csrfToken: token });
  }

  const cookieToken = req.cookies[COOKIE_NAME];
  const headerToken = req.headers[HEADER_NAME];
  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }

  // тут логіка захищеного endpoint'у
  return res.status(200).json({ message: 'Protected action succeeded' });
}

// /hooks/useCsrf.ts
import { useEffect } from 'react';

export function useCsrf() {
  useEffect(() => {
    fetch('/api/csrf', { credentials: 'include' })
      .then(r => r.json())
      .then(data => {
        (window as any).csrfToken = data.csrfToken;
      });
  }, []);
}

export async function csrfFetch(input: RequestInfo, init: RequestInit = {}) {
  const token = (window as any).csrfToken;
  const headers = new Headers(init.headers);
  headers.set('x-csrf-token', token);
  return fetch(input, { ...init, headers, credentials: 'include' });
}

Після імпорту useCsrf у компонент, токен буде отриманий під час монтування, а кожний виклик csrfFetch автоматично додасть потрібний заголовок.

Висновок і подальші кроки

Захист від CSRF у застосунках Next.js не потребує складних рішень – достатньо поєднати SameSite cookies, прапорець Secure та шаблон double‑submit token. Дотримуючись рекомендацій OWASP, CWE та принципу least‑privilege, ви створюєте надійний бар’єр проти неавторизованих запитів. Якщо потрібен аудит безпеки, допомога з інтеграцією або підвищення рівня захисту всієї платформи, звертайтеся до Coderia.it – разом побудуємо застосунок, стійкий до найновіших загроз.

Почнімо

Маєте проєкт на думці?

Опишіть його кількома реченнями — відповім протягом 24 годин із безкоштовною оцінкою та пропозицією стеку.