Sending CSRF name/value as request header does not work with Apache
Slim-Csrf supports reading the token names and values from HTTP request headers, which is useful when using AJAX and such. This functionality is implemented here: https://github.com/slimphp/Slim-Csrf/blob/1.x/src/Guard.php#L449
Unfortunately, this does not seem to work when serving PHP via fcgid on the Apache web server, as the HTTP headers are stripped.
It's taken me a lot of debugging to get here, but once you set the Apache LogLevel to LogLevel trace1 and send a request with the headers set, you'll see errors such as the following in the Apache error log:
[Wed Oct 29 14:32:35.792102 2025] [core:trace1] [pid 16352:tid 1960] util_script.c(77): [client ::1:50027] Not exporting header with invalid name as envvar: name, referer: http://localhost/system/create
[Wed Oct 29 14:32:35.792102 2025] [core:trace1] [pid 16352:tid 1960] util_script.c(77): [client ::1:50027] Not exporting header with invalid name as envvar: value, referer: http://localhost/system/create
Using Apache's mod_forensic, we can see the request coming in with the header intact:
+14756:690224c8:0|POST /system/create HTTP/1.1|Host:localhost|Connection:keep-alive|Content-Length:363|sec-ch-ua-platform:"Windows"|sec-ch-ua:"Microsoft Edge";v="141", "Not?A_Brand";v="8", "Chromium";v="141"|sec-ch-ua-mobile:?0|csrf_name:csrf690202736056c|X-Requested-With:XMLHttpRequest|User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0|Accept:application/json|Content-Type:application/json|csrf_value:xMqmCCFGUWeSDFlpR554QtPrjbaBjhv1d7YlrN1Uya7x+MJrFXBkVKVoaQpx+k4k4om41+K9IsdBj0Gbuzb/mQ==|Origin:http%3a//localhost|Sec-Fetch-Site:same-origin|Sec-Fetch-Mode:cors|Sec-Fetch-Dest:empty|Referer:http%3a//localhost/system/create|Accept-Encoding:gzip, deflate, br, zstd|Accept-Language:en-GB,en;q=0.9,en-US;q=0.8|Cookie:PHPSESSID=cjlal9vpd00c4ga4ldcmnovmqg
-14756:690224c8:0
This message is logged by Apache here: https://github.com/apache/httpd/blob/trunk/server/util_script.c#L72
To me, it looks like Apache doesn't like that the header contains an underscore (csrf_name and csrf_value become name and value respectively).
It might be useful to be able to customise the logic for the Guard::getTokenNameKey and Guard::getTokenValueKey logic.
As it stands, I've changed the logic to use a hyphen (-) rather than an underscore (-) by subclassing the Guard and re-implementing the two methods, and that resolved the problem.
Hi @euantorano Yes, to make the TokenNameKey and TokenValueKey configurable would be the best solution. Until this is implemented, you may also use a custom middleware to copy the values from one header to another header, so that the Guard class can read the value with an underscore. Example (not tested):
final class CsrfHeaderFixMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$request->hasHeader('csrf_name') && $request->hasHeader('csrf-name')) {
$request = $request->withHeader('csrf_name', $request->getHeader('csrf-name'));
}
if (!$request->hasHeader('csrf_value') && $request->hasHeader('csrf-value')) {
$request = $request->withHeader('csrf_value', $request->getHeader('csrf-value'));
}
return $handler->handle($request);
}
}
Usage
$app->add(new Slim\Csrf\Guard($responseFactory));
$app->add(new App\Middleware\CsrfHeaderFixMiddleware()); // <--- after the Guard middleware to run it before the execution of the Guard middlewaere
Feel free to create a PR that adds the needed setter-Methods.
This is also solved in the 2.x branch, so we probably should look at tagging 2.0 to release it.