Захист CSRF
Concept Core захищає змінювальні HTTP-запити від підробки між сайтами (CSRF) через
синхронізаторний токен у сесії. Перевірка виконується в
VerifyCsrfTokenMiddleware до контролера; токен для форм і AJAX надає
CsrfTokenManager.
Див. також: Middleware → CSRF,
Представлення → глобальні змінні (csrf_token).
Класи ядра
| Клас | Роль |
|---|---|
Concept\Core\Http\Security\CsrfTokenManager |
Генерація та validate() токена в сесії |
VerifyCsrfTokenMiddleware |
Перевірка POST / PUT / PATCH / DELETE |
ShareViewDataMiddleware |
ViewKey::CSRF_TOKEN → Twig csrf_token |
Concept\Core\Http\Exceptions\Security\CsrfException |
419 Page Expired |
CsrfTokenManager не має окремого service provider — резолвиться автовайрингом
(залежність SessionInterface).
Як це працює
- При першому зверненні
CsrfTokenManager::getToken()генерує 64-символьний hex-токен (random_bytes(32)) і зберігає його в сесії під ключемSessionKey::CSRF_TOKEN(_csrf_token). ShareViewDataMiddlewareпередає токен у Twig як{{ csrf_token }}.- Для POST, PUT, PATCH і DELETE middleware порівнює токен із запиту з сесійним через
hash_equals(). - Якщо токен відсутній або не збігається — кидається
CsrfException(HTTP 419 Page Expired).
Підключення middleware
У routes/web.php (або на групі маршрутів) зареєструйте перевірку після обробника валідації, але до зміни стану:
$router->lazyMiddleware(HandleValidationExceptionMiddleware::class);
$router->lazyMiddleware(VerifyCsrfTokenMiddleware::class);
$router->lazyMiddleware(StorePreviousUrlMiddleware::class);
$router->lazyMiddleware(ShareViewDataMiddleware::class);
ShareViewDataMiddleware має бути в стеку, щоб шаблони отримували актуальний токен.
Сесія потрібна — реєструється через SessionServiceProvider у
bootstrap/providers/app.php.
Форми (HTML)
У кожній формі, що змінює дані, додайте приховане поле з іменем _csrf_token:
У layout додайте meta для AJAX (опційно):
<meta name="csrf-token" content="{{ csrf_token }}">
<form method="post" action="{{ uri('cabinet.profile.update') }}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token }}">
{# ... поля форми ... #}
</form>
Поле _csrf_token автоматично виключається з даних валідації в
FormRequest (глобальний $globalExcept), тому воно не потрапляє в DTO.
AJAX та API з браузера
VerifyCsrfTokenMiddleware шукає токен у такому порядку:
- Поле
_csrf_tokenуgetParsedBody()(форма або JSON body) - Заголовок
X-CSRF-TOKEN(HttpHeader::X_CSRF_TOKEN) - Заголовок
X-XSRF-TOKEN(HttpHeader::X_XSRF_TOKEN) — черезurldecode()
fetch('/api/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(payload)
});
Для чистих зовнішніх API без сесії CSRF зазвичай не застосовують — винесіть такі маршрути в окрему групу без VerifyCsrfTokenMiddleware і використовуйте іншу автентифікацію (Bearer, API key).
Помилки та відповіді
CsrfException має код HttpStatusCode::PAGE_EXPIRED (419). Error handler
(Whoops / production handler) формує HTML або JSON залежно від запиту — див.
Відповіді.
Типові причини 419:
- Сесія прострочена або відсутня cookie
- Форма відкрита давно, токен у сесії вже змінився
- Подвійна відправка з кешованої сторінки без актуального
csrf_token
Програмний доступ
У сервісах можна отримати менеджер із контейнера:
use Concept\Core\Http\Security\CsrfTokenManager;
/** @var CsrfTokenManager $csrf */
$csrf = $container->get(CsrfTokenManager::class);
$token = $csrf->getToken();
$valid = $csrf->validate($submittedToken);
Для перевірки в тестах передавайте той самий токен, що в сесії тестового клієнта.
Захищені методи: POST, PUT, PATCH, DELETE
(VerifyCsrfTokenMiddleware::PROTECTED_METHODS). GET, HEAD, OPTIONS не перевіряються —
не виконуйте зміну стану через GET.