Пошук уроків, статей та іншого контенту
Захистите застосунок за допомогою HTTP security headers, CSP та безпечних налаштувань cookies.
HTTP security headers повідомляють браузеру, як саме обробляти сторінку та її ресурси. Вони допомагають:
заборонити завантаження небезпечних типів ресурсів;
зменшити ризик XSS;
обмежити використання камери, мікрофона та геолокації;
заборонити вбудовування застосунку в iframe;
захистити cookie від доступу JavaScript;
примусово використовувати HTTPS.
У Next.js заголовки можна налаштувати глобально в next.config.js, а для динамічної CSP — у middleware.ts.
Створіть або змініть файл next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
const headers = [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
"form-action 'self'",
"img-src 'self' data: blob:",
"font-src 'self'",
"style-src 'self' 'unsafe-inline'",
"script-src 'self'",
].join('; '),
},
];
if (process.env.NODE_ENV === 'production') {
headers.push({
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
});
}
return [
{
source: '/(.*)',
headers,
},
];
},
};
module.exports = nextConfig;Після зміни конфігурації перезапустіть сервер розробки або повторно зберіть застосунок.
X-Content-Type-OptionsX-Content-Type-Options: nosniffЗабороняє браузеру вгадувати MIME-тип ресурсу. Наприклад, браузер не повинен обробляти файл як JavaScript лише тому, що його вміст схожий на JavaScript.
Referrer-PolicyReferrer-Policy: strict-origin-when-cross-originОбмежує інформацію, яку браузер передає в заголовку Referer під час переходів між сайтами.
У цьому режимі:
для переходів у межах одного сайту передається повний URL;
для іншого сайту передається лише origin;
під час переходу з HTTPS на HTTP referrer не передається.
Permissions-PolicyPermissions-Policy: camera=(), microphone=(), geolocation=()Повністю забороняє застосунку використовувати камеру, мікрофон і геолокацію.
Якщо конкретна функція потрібна, її можна дозволити, наприклад:
Permissions-Policy: camera=(self), microphone=(), geolocation=()Strict-Transport-SecurityStrict-Transport-Security: max-age=31536000; includeSubDomainsПовідомляє браузеру, що сайт потрібно відкривати лише через HTTPS.
Цей заголовок не варто вмикати для локальної розробки. Після його отримання браузер може автоматично замінювати HTTP на HTTPS протягом указаного часу.
frame-ancestorsУ прикладі ця директива забороняє вбудовувати застосунок в iframe:
Content-Security-Policy: frame-ancestors 'none'Це захищає від clickjacking — атаки, під час якої користувача змушують натискати елементи іншої сторінки, невидимої поверх справжнього інтерфейсу.
Content Security Policy, або CSP, визначає, звідки сторінка може завантажувати:
JavaScript;
CSS;
зображення;
шрифти;
фрейми;
інші типи ресурсів.
Наприклад:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'Це означає:
за замовчуванням дозволені ресурси лише з поточного origin;
JavaScript дозволений лише з поточного origin;
плагіни на кшталт Flash заборонені.
default-src 'self' — політика за замовчуванням для всіх типів ресурсів.
script-src — джерела JavaScript.
style-src — джерела стилів.
img-src — джерела зображень.
font-src — джерела шрифтів.
connect-src — джерела для fetch, WebSocket та інших мережевих запитів.
object-src 'none' — заборона плагінів.
base-uri 'self' — обмеження для HTML-елемента <base>.
frame-ancestors 'none' — заборона вбудовування сторінки в iframe.
form-action 'self' — форми можуть надсилати дані лише на поточний origin.
unsafe-evalДиректива:
script-src 'self' 'unsafe-eval'дозволяє виконання рядків як JavaScript через механізми на кшталт eval. Це послаблює захист від XSS.
Не додавайте 'unsafe-eval', якщо цього не вимагає конкретна бібліотека. Спочатку перевірте, чи не можна налаштувати бібліотеку або замінити її.
unsafe-inline для скриптів небезпечнийДиректива:
script-src 'self' 'unsafe-inline'дозволяє inline-скрипти та обробники подій у HTML. Якщо в сторінку потрапить небезпечний HTML, такий скрипт може виконатися.
Для скриптів краще використовувати nonce — одноразове випадкове значення.
Для динамічної CSP можна створювати nonce на кожен HTTP-запит. Next.js використовує nonce для дозволених скриптів під час серверного рендерингу.
Створіть файл middleware.ts у корені проєкту:
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Створюємо одноразове значення для поточного запиту.
const nonce = btoa(crypto.randomUUID());
const contentSecurityPolicy = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
"font-src 'self'",
"connect-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"frame-ancestors 'none'",
"form-action 'self'",
].join('; ');
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicy,
);
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicy,
);
return response;
}
export const config = {
matcher: [
/*
* Не запускаємо middleware для статичних файлів Next.js.
*/
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};Nonce має бути:
випадковим;
новим для кожного запиту;
достатньо непередбачуваним;
однаковим у CSP і в атрибуті nonce дозволеного скрипту.
'strict-dynamic' дозволяє скриптам, авторизованим через nonce, завантажувати інші скрипти. Це зручно для складних застосунків, але CSP потрібно перевірити у всіх підтримуваних браузерах.
Використання динамічної CSP може вплинути на статичну оптимізацію сторінок, оскільки nonce створюється під час запиту. Також після її додавання потрібно перевірити всі зовнішні ресурси, які використовує застосунок.
Під час налаштування CSP помилки видно в консолі браузера. Наприклад:
Refused to load the script because it violates the following
Content Security Policy directive: "script-src 'self'".Алгоритм перевірки:
Відкрийте застосунок у браузері.
Відкрийте DevTools.
Перейдіть на вкладку Console.
Знайдіть повідомлення про порушення CSP.
Визначте, який ресурс було заблоковано.
Додайте лише потрібне джерело до відповідної директиви.
Повторно перевірте сторінку.
Не додавайте 'unsafe-inline', 'unsafe-eval' або * лише для того, щоб швидко прибрати помилку.
Cookie часто використовують для зберігання ідентифікатора сесії. Сервер повинен установлювати такі cookie з атрибутами:
HttpOnly — JavaScript не може прочитати cookie через document.cookie;
Secure — cookie передається лише через HTTPS;
SameSite — обмежує надсилання cookie у cross-site запитах;
Path=/ — cookie доступна для всього застосунку;
Max-Age або Expires — обмежує час життя cookie.
Для сесійної cookie зазвичай використовують:
HttpOnly; Secure; SameSite=Lax; Path=/Приклад для App Router. Створіть файл app/api/session/route.ts:
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function POST() {
// У реальному застосунку токен потрібно зберегти на сервері
// та пов'язати його з користувачем і терміном дії сесії.
const sessionToken = crypto.randomUUID();
const cookieStore = await cookies();
cookieStore.set('session', sessionToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7,
});
return NextResponse.json({ authenticated: true });
}
export async function DELETE() {
const cookieStore = await cookies();
cookieStore.set('session', '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 0,
});
return NextResponse.json({ authenticated: false });
}У цьому прикладі:
токен недоступний клієнтському JavaScript;
у production cookie передається лише через HTTPS;
SameSite=Lax зменшує ризик CSRF для типових сценаріїв;
cookie автоматично видаляється через сім днів;
DELETE-запит видаляє cookie.
Токен у прикладі демонстраційний. У реальному застосунку сервер має зберігати сесію або її хеш у сховищі даних. Не зберігайте в cookie пароль, секретний ключ або інші довготривалі секрети.
__Host-Для cookie, яка повинна належати лише поточному host, можна використовувати назву з префіксом __Host-:
cookieStore.set('__Host-session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7,
});Така cookie повинна:
використовувати Secure;
мати Path=/;
не мати атрибута Domain.
Префікс __Host- допомагає не дозволити піддоменам встановлювати cookie для основного host. Під час локальної розробки така cookie може не працювати без HTTPS, тому для production її потрібно перевірити окремо.
SameSiteSameSite=StrictCookie майже не надсилається під час переходів з іншого сайту.
Перевага — сильніший захист від CSRF. Недолік — користувач може втратити очікувану сесію після переходу на сайт із зовнішнього посилання.
SameSite=LaxCookie надсилається для звичайних переходів верхнього рівня, але не для більшості cross-site POST-запитів.
Це практичний варіант за замовчуванням для багатьох сесійних cookie.
SameSite=NoneCookie надсилається у cross-site контекстах, але разом із ним обов’язково потрібен атрибут Secure.
Використовуйте цей режим лише тоді, коли cross-site сценарій справді необхідний.
HttpOnlyНе встановлюйте сесійну cookie без httpOnly. Інакше будь-який JavaScript, який виконався на сторінці, може прочитати токен:
cookieStore.set('session', sessionToken, {
httpOnly: false,
});Для сесійних cookie це небезпечне налаштування.
Secure під час HTTP-розробкиCookie з Secure не передається через звичайний HTTP. Якщо локальний застосунок працює без HTTPS, cookie може не зберігатися.
Можна використовувати умовне значення:
secure: process.env.NODE_ENV === 'production'У production застосунок має працювати через HTTPS.
Access-Control-Allow-Origin: * без потребиCORS і security headers — різні механізми. Не дозволяйте всі origin, якщо API працює з cookie або приватними даними. Для приватних запитів потрібно явно визначати дозволені origin.
Такі значення послаблюють політику:
script-src *
default-src *
script-src 'unsafe-inline' 'unsafe-eval'Починайте з мінімальних дозволів і додавайте лише ті джерела, які справді потрібні застосунку.
HSTS кешується браузером. Якщо випадково ввімкнути його для локального домену або тестового середовища, браузер може примусово перенаправляти HTTP-запити на HTTPS навіть після зміни конфігурації.
SameSite зменшує ризик CSRF, але не замінює перевірку автентифікації та авторизації на сервері. Кожен endpoint повинен окремо перевіряти, чи має поточна сесія право виконувати операцію.
Після налаштування відкрийте DevTools і перевірте вкладку Network.
Для HTML-документа повинні бути присутні заголовки на кшталт:
Content-Security-Policy: default-src 'self'; ...
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()Для production через HTTPS також перевірте:
Strict-Transport-Security: max-age=31536000; includeSubDomainsУ відповіді після входу перевірте Set-Cookie:
Set-Cookie: session=...; Path=/; Max-Age=604800; HttpOnly; Secure; SameSite=LaxНалаштовуйте глобальні HTTP security headers через next.config.js або middleware.
Використовуйте CSP, щоб обмежити джерела скриптів та інших ресурсів.
Для динамічної CSP застосовуйте одноразовий nonce замість 'unsafe-inline'.
Встановлюйте сесійні cookie з HttpOnly, Secure, SameSite і обмеженим часом життя.
Не вмикайте HSTS для локальної розробки.
Перевіряйте заголовки та CSP-помилки через DevTools.
Застосовуйте мінімально необхідні дозволи, а не універсальні значення на кшталт *.