Na przykładzie PHPiggy zobaczymy jak można podejść do zagadnienia CSRF przy pomocy middleware, bo poza tym to już zdaje się temat bardzo solidnie omówiliśmy. Do dzieła.
Na początek SessionMiddleware:
class SessionMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
if (session_status() === PHP_SESSION_ACTIVE) {
throw new SessionException("Session already active.");
}
if (headers_sent($filename, $line)) {
throw new SessionException("Headers already sent. Consider enabling output buffering. Data outputted from {$filename} - Line: {$line}");
}
session_set_cookie_params([
'secure' => $_ENV['APP_ENV'] === "production",
'httponly' => true,
'samesite' => 'lax'
]);
session_start();
$next();
session_write_close();
}
}
Już omawialiśmy co autor miał na myśli i jak to zaimplementować u siebie. Teraz tworzenie tokena w sesji:
<?php
declare(strict_types=1);
namespace App\Middleware;
use Framework\Contracts\MiddlewareInterface;
use Framework\TemplateEngine;
class CsrfTokenMiddleware implements MiddlewareInterface
{
public function __construct(private TemplateEngine $view)
{
}
public function process(callable $next)
{
$_SESSION['token'] = $_SESSION['token'] ?? bin2hex(random_bytes(32));
$this->view->addGlobal('csrfToken', $_SESSION['token']);
$next();
}
}
Ciekawa sprawa – jeżeli sesja jest tokena pozbawiona to on jest w sesji tworzony. Dopiero poprzez sesję jest wstrzykiwany przez TemplateEngine do formularza jako value pola input typu hidden.
Teraz sprawdzanie tokena:
class CsrfGuardMiddleware implements MiddlewareInterface
{
public function process(callable $next)
{
$requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);
$validMethods = ['POST', 'PATCH', 'DELETE'];
if (!in_array($requestMethod, $validMethods)) {
$next();
return;
}
if ($_SESSION['token'] !== $_POST['token']) {
redirectTo('/');
}
unset($_SESSION['token']);
$next();
}
}
Jeżeli metoda nie wymaga tokena – idziemy dalej.
Jeżeli tokeny z „input type hidden token” się nie zgadzają z tym, co w sesji to ktoś tu majstruje zamiast „legitnie” wysyłać formularz z naszej strony – redirect zatem.
Jeżeli wszystko ok, to znaczy warunki „failowe” nie padły, to jeszcze zanim zawołamy next, usuwamy token, który już został użyty.