
O primeiro POST do formulário via AJAX grava direitinho e o segundo devolve 403 com The action you requested is not allowed. Não é token errado: é token velho. Com $regenerate = true em app/Config/Security.php, cada requisição válida queima o hash e gera outro, e o seu JavaScript continua mandando o que veio no carregamento da página.
Guardo o hash numa meta tag do layout, leio dali na hora do envio e regravo com o valor que o controller devolve:
<!-- na view/layout -->
<meta name="csrf-hash" content="<?= csrf_hash() ?>">
// no JS
const meta = document.querySelector('meta[name="csrf-hash"]');
const r = await fetch('/pedido/salvar', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': meta.content, 'Accept': 'application/json' },
body: new FormData(document.getElementById('form-pedido'))
});
const dados = await r.json();
meta.content = dados.csrf; // hash novo pro próximo POST No controller, devolva o hash junto com a resposta. Como o CodeIgniter regenera antes de o seu método rodar, o csrf_hash() daqui já é o valor válido para a próxima chamada:
return $this->response->setJSON([
'ok' => true,
'csrf' => csrf_hash(),
]); Se o header tiver outro nome, o filtro nem enxerga o token. Confira as propriedades em app/Config/Security.php: $headerName (padrão X-CSRF-TOKEN), $tokenName, $regenerate e $redirect. Em API deixe o $redirect em false — senão a falha vira um redirect que o fetch engole e você recebe HTML onde esperava JSON.
Não troque os dois helpers: csrf_token() devolve o nome do campo e csrf_hash() devolve o valor. Quem viaja no header é o hash. Se preferir mandar no corpo, o filtro olha o POST antes do header.
Dá pra matar o sintoma com $regenerate = false: um hash serve a sessão inteira. Funciona, mas você abre mão da proteção contra reenvio do token — em produção prefiro pagar as três linhas de JavaScript.
O certbot diz que renovou, o navegador insiste que o certificado expirou. Compare as datas com openssl e crie o…
O campo aparece cinza no painel avisando que o valor vem de arquivo? Descubra o caminho da chave, remova do…
Chamar array_filter sem callback descarta 0, '0' e false junto com os vazios. Veja o callback certo, o ARRAY_FILTER_USE_BOTH e…
O upload do tema ou do vídeo morre com 413 e o php.ini não resolve? Quem corta é o nginx:…
O cliente pediu exclusão pela LGPD mas a nota fiscal precisa ficar guardada. A saída é anonimizar o pedido, não…
504 no nginx quando a API de IA demora? Quem desiste primeiro é o nginx, não o PHP. Enfileire o…