-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
PHP Version
8.4
CodeIgniter4 Version
7.4.1
CodeIgniter4 Installation Method
Composer (as dependency to an existing project)
Which operating systems have you tested for this bug?
macOS
Which server did you use?
fpm-fcgi
Environment
development
Database
MySQL 5.6
What happened?
After upgrading from CI4 4.7.0 to 4.7.1, all POST requests with Content-Type: application/json fail when calling $this->request->getJSON().
The CSRF filter modifies $request->body, URL-encoding the raw JSON string. This causes json_decode() to fail with "Failed to parse JSON string. Error: Syntax error".
Debug output proving the issue:
php://input (raw, untouched — correct):
length: 462
content: {"entity_type":"pages","uuid":"171354f4-..."}
$request->getBody() (after CSRF filter ran — corrupted):
length: 643
content: %7B%22entity_type%22%3A%22pages%22%2C%22uuid%22%3A%22171354f4-...
php://input contains valid JSON (462 bytes).
$request->getBody() returns the same data but URL-encoded (643 bytes), ending with =.
This indicates the CSRF filter treats the JSON body as a form field key and runs it through URL encoding (likely via parse_str() or similar).
This worked correctly in 4.7.0 — the CSRF filter did not alter the body of application/json requests.
Steps to Reproduce
Fresh CI4 4.7.1 install (or upgrade from 4.7.0)
Enable CSRF filter globally in app/Config/Filters.php:
public array $globals = [
'before' => [
'csrf',
],
];
Create a simple controller:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class ApiTest extends Controller
{
public function save()
{
$rawInput = file_get_contents('php://input');
$body = $this->request->getBody();
log_message('debug', 'php://input length: ' . strlen($rawInput));
log_message('debug', 'getBody() length: ' . strlen($body ?? ''));
log_message('debug', 'php://input first 100: ' . substr($rawInput, 0, 100));
log_message('debug', 'getBody() first 100: ' . substr($body ?? '', 0, 100));
// Throws: "Failed to parse JSON string. Error: Syntax error"
$data = $this->request->getJSON(true);
return $this->response->setJSON(['success' => true, 'data' => $data]);
}
}
Add route:
$routes->post('api/test', 'ApiTest::save');
Send a JSON POST request with a valid CSRF token in the header:
fetch('/api/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ name: 'test', value: 'hello world' })
});
Result: 500 error — Failed to parse JSON string. Error: Syntax error
Logs confirm: getBody() returns a URL-encoded string instead of raw JSON.
Expected Output
$this->request->getJSON() should return the decoded JSON object, identical to:
json_decode(file_get_contents('php://input'));
The CSRF filter should not modify $request->body when the Content-Type is application/json. The CSRF token should be read from the X-CSRF-TOKEN header — which is the standard approach for AJAX/JSON requests — without altering the request body.
In 4.7.0, this worked correctly: the body was preserved as raw JSON after the CSRF filter ran.
Anything else?
No response