🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
⚠️ Malware / Trojaner / VirenLumma Stealer – dllhost.exe Hollowing, C2 Domains & Payload Extraction(01.09.2026 um 17:19 Uhr)
🔧 AI Nachrichten Simcha Kosman AMA: Owning ChatGPT's Secure Sandbox(03.09.2026 um 07:41 Uhr)
⚠️ Malware / Trojaner / VirenThe Gentlemen Ransomware Analysis: Go Obfuscated(04.09.2026 um 12:05 Uhr)
⚠️ Malware / Trojaner / VirenTengu, a Mirai-style Linux and IoT botnet(06.09.2026 um 15:27 Uhr)
🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
⚠️ Malware / Trojaner / VirenSofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht(06.09.2026 um 08:00 Uhr)
⚠️ Malware / Trojaner / VirenLumma Stealer – dllhost.exe Hollowing, C2 Domains & Payload Extraction(01.09.2026 um 17:19 Uhr)
🔧 AI Nachrichten Simcha Kosman AMA: Owning ChatGPT's Secure Sandbox(03.09.2026 um 07:41 Uhr)
⚠️ Malware / Trojaner / VirenThe Gentlemen Ransomware Analysis: Go Obfuscated(04.09.2026 um 12:05 Uhr)
⚠️ Malware / Trojaner / VirenTengu, a Mirai-style Linux and IoT botnet(06.09.2026 um 15:27 Uhr)

🔧 Programmierung 🕛 kürzlich 9 Min Lesezeit
0

CodeIgniter 4 CSRF Protection in AJAX Forms — The Complete Fix

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Difficulty: Intermediate | Read time: 12 min | Framework: CodeIgniter 4









Why CSRF Breaks Your AJAX Forms



You build a perfectly working AJAX form. It works fine in testing. Then suddenly — "Page Expired" or a 419 error. You refresh, it works once, then breaks again.



This is CodeIgniter 4's CSRF protection doing its job — but if you don't handle it correctly in AJAX, it will drive you crazy.



In this tutorial I'll show you exactly why it happens and give you production-ready patterns to fix it permanently — for single forms, multiple forms, and even global AJAX setups.




What we'll cover: How CI4 CSRF works, why AJAX breaks it, token refresh after every request, global jQuery fix, Fetch API fix, and real-world examples from CRM and ticket system projects.










How CI4 CSRF Works (Quick Recap)



Every form submission needs a valid CSRF token. CI4 generates a token, stores it in a cookie, and expects it back in every POST request.




CODE
// app/Config/Security.php — default settings
public string $tokenName = 'csrf_test_name'; // field name
public string $headerName = 'X-CSRF-TOKEN'; // header name for AJAX
public bool $regenerate = true; // ← THIS causes most AJAX issues
public bool $redirect = true; // redirects on failure (not great for AJAX)






The problem: when $regenerate = true, the token changes after every single request. So if your AJAX doesn't update the token after each call, the second request will fail.









The 3 Most Common CSRF AJAX Mistakes






❌ Mistake 1 — Not sending the token at all






CODE
// WRONG — no CSRF token
$.post('/tickets/assign', {
ticket_id: 1,
staff_id: 5
}, function(res) { ... });









❌ Mistake 2 — Sending it once, never updating






CODE
// WRONG — token sent but never refreshed after response
$.post('/tickets/assign', {
ticket_id: 1,
staff_id: 5,
csrf_test_name: '<?= csrf_hash() ?>' // gets stale after first request
}, function(res) { ... });









❌ Mistake 3 — Wrong config for AJAX (redirect on failure)






CODE
// WRONG for AJAX — CI4 redirects instead of returning JSON error
public bool $redirect = true;












The Complete Fix — Step by Step






Step 1 — Update Security Config for AJAX






CODE
// app/Config/Security.php
public string $csrfProtection = 'cookie'; // or 'session'
public string $tokenName = 'csrf_test_name';
public string $headerName = 'X-CSRF-TOKEN';
public bool $regenerate = true; // keep true for security
public bool $redirect = false; // IMPORTANT: false for AJAX apps
public bool $samesite = 'Lax';






Setting $redirect = false means CI4 will return a proper response instead of redirecting — so your AJAX error handlers can catch it.









Step 2 — Return New Token in Every AJAX Response



In every controller method that handles AJAX, return the new token:




CODE
<?php
// app/Controllers/Tickets.php

public function assign(): \CodeIgniter\HTTP\ResponseInterface
{
if (!$this->request->isAJAX()) {
return $this->response->setStatusCode(403);
}

$ticketId = (int) $this->request->getPost('ticket_id');
$staffId = (int) $this->request->getPost('staff_id');

$assigned = $this->ticketModel->assignTicket($ticketId, $staffId);

// Always return new CSRF token in response
return $this->response->setJSON([
'success' => $assigned,
'message' => $assigned ? 'Ticket assigned!' : 'Assignment failed.',
'csrf_token' => csrf_hash(), // ← send new token back
'csrf_name' => csrf_token(), // ← send token name too
]);
}












Step 3A — jQuery Global AJAX Fix (Recommended)



Set this up once in your main layout — it handles CSRF for ALL jQuery AJAX requests automatically:




CODE
// Put this in your main layout or app.js — runs once, fixes everything

var csrfName = '<?= csrf_token() ?>';
var csrfToken = '<?= csrf_hash() ?>';

// Add CSRF to every jQuery AJAX request automatically
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (settings.type === 'POST' || settings.type === 'PUT' || settings.type === 'DELETE') {
// Add as header (works with CI4 headerName config)
xhr.setRequestHeader('X-CSRF-TOKEN', csrfToken);

// Also add to data if it's a form POST
if (typeof settings.data === 'string') {
settings.data += '&' + csrfName + '=' + csrfToken;
} else if (typeof settings.data === 'object' && settings.data !== null) {
settings.data[csrfName] = csrfToken;
}
}
}
});

// After every AJAX response, update the token
$(document).ajaxComplete(function(event, xhr, settings) {
try {
var response = JSON.parse(xhr.responseText);
if (response.csrf_token) {
csrfToken = response.csrf_token;
csrfName = response.csrf_name || csrfName;
}
} catch(e) {
// Not a JSON response — that's fine
}
});






With this setup, you never need to manually add the token to individual AJAX calls again.









Step 3B — Individual AJAX Call Fix



If you prefer per-call control:




CODE
// Single AJAX call with CSRF — with token refresh
function assignTicket(ticketId, staffId) {
var data = {
ticket_id: ticketId,
staff_id: staffId
};

// Add CSRF token
data[csrfName] = csrfToken;

$.post('<?= base_url('tickets/assign') ?>', data, function(res) {
if (res.csrf_token) {
// Update token for next request
csrfToken = res.csrf_token;
csrfName = res.csrf_name || csrfName;
}

if (res.success) {
toastr.success(res.message);
} else {
toastr.error(res.message);
}
}).fail(function(xhr) {
if (xhr.status === 403) {
toastr.error('Session expired. Please refresh the page.');
}
});
}












Step 3C — Fetch API Fix (No jQuery)



If you're using vanilla JS or React:




CODE
// CSRF with Fetch API
let csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
let csrfName = document.querySelector('meta[name="csrf-name"]').getAttribute('content');

async function postData(url, payload) {
const body = new FormData();
body.append(csrfName, csrfToken);

Object.entries(payload).forEach(([key, value]) => {
body.append(key, value);
});

const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: body
});

const data = await response.json();

// Update token from response
if (data.csrf_token) {
csrfToken = data.csrf_token;
}

return data;
}

// Usage
const result = await postData('/tickets/assign', { ticket_id: 1, staff_id: 5 });






Add these meta tags in your layout <head>:




CODE
<!-- app/Views/layouts/main.php -->
<meta name="csrf-token" content="<?= csrf_hash() ?>">
<meta name="csrf-name" content="<?= csrf_token() ?>">












Real-World Example — Lead Send Email Form



Here's a complete real-world example — a modal form that sends emails to leads with CSRF handled properly:




CODE
<!-- View: leads/send_email_modal.php -->
<div class="modal fade" id="sendEmailModal">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Send Email to Lead</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">To</label>
<input type="email" id="lead_email" class="form-control" />
</div>
<div class="mb-3">
<label class="form-label">CC</label>
<input type="text" id="lead_cc" class="form-control" placeholder="Comma separated emails" />
</div>
<div class="mb-3">
<label class="form-label">Subject</label>
<input type="text" id="email_subject" class="form-control" />
</div>
<div class="mb-3">
<label class="form-label">Message</label>
<textarea id="email_body" class="form-control" rows="5"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="sendEmailBtn">
<span class="spinner-border spinner-border-sm d-none" id="sendSpinner"></span>
Send Email
</button>
</div>
</div>
</div>
</div>

<script>
$('#sendEmailBtn').on('click', function() {
var $btn = $(this);
var $spinner = $('#sendSpinner');

// Basic validation
if (!$('#lead_email').val()) {
toastr.error('Please enter recipient email.');
return;
}

// Show loading state
$btn.prop('disabled', true);
$spinner.removeClass('d-none');

var data = {
to: $('#lead_email').val(),
cc: $('#lead_cc').val(),
subject: $('#email_subject').val(),
body: $('#email_body').val()
};

// CSRF added automatically by $.ajaxSetup (from global fix above)
$.post('<?= base_url('leads/send_email') ?>', data, function(res) {

// Update CSRF token
if (res.csrf_token) {
csrfToken = res.csrf_token;
}

if (res.success) {
toastr.success('Email sent successfully!');
$('#sendEmailModal').modal('hide');
} else {
toastr.error(res.message || 'Failed to send email.');
}

}).fail(function(xhr) {
if (xhr.status === 403) {
toastr.error('CSRF token expired. Please refresh the page.');
} else {
toastr.error('Something went wrong. Try again.');
}
}).always(function() {
$btn.prop('disabled', false);
$spinner.addClass('d-none');
});
});
</script>












What If You Want to Disable CSRF for Specific Routes?



Sometimes you need to exclude API endpoints from CSRF (like webhooks). Do it in the filter config:




CODE
// app/Config/Filters.php
public array $globals = [
'before' => [
'csrf' => ['except' => [
'api/*', // exclude all /api/ routes
'webhooks/stripe', // exclude specific webhook
]],
],
];












Common Pitfalls






⚠️ 1. Using the same token for multiple rapid requests



If you fire multiple AJAX calls in quick succession and $regenerate = true, only the first one will succeed. Solution: either set $regenerate = false (less secure) OR queue your requests so they run one at a time.






⚠️ 2. Token in URL (GET request CSRF)



Never put CSRF tokens in GET request URLs. CSRF protection is only needed for state-changing requests — POST, PUT, DELETE.






⚠️ 3. SameSite cookie issues on some browsers



If your app is embedded in an iframe or served cross-origin, set:




CODE
public string $samesite = 'None'; // requires HTTPS









⚠️ 4. AJAX response is HTML instead of JSON



If CI4 redirects instead of returning JSON, it means $redirect = true in Security.php. Change it to false for AJAX apps.









What to Build Next





  • CodeIgniter 4 REST API — build and secure endpoints with token auth


  • CodeIgniter 4 File Upload to AWS S3 — AJAX file upload with progress bar


  • CodeIgniter 4 + jQuery AJAX CRUD — full CRUD without page reload









Conclusion



CSRF protection in CodeIgniter 4 is solid — but it requires a small amount of setup to work properly with AJAX. The global $.ajaxSetup + ajaxComplete pattern is the cleanest solution — set it up once and never worry about it again.



Follow me for more CodeIgniter production tutorials — 3 new articles every week. 🙌






Senior PHP Developer · 12+ years building production systems on CodeIgniter, Laravel & WordPress

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 37%
🟡 In Evaluierung 34%
🟢 Keine Auswirkung 12%
Spannende Innovation 17%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Creator Panel – One Creator, Full Production: Der neue Creator Workflow
1 Quelle
ChatGPT showing blank screen [Fix]
1 Quelle
Sofort deinstallieren: Diese 19 Browser-Erweiterungen sind mit Malware verseucht
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten CodeIgniter 4 CSRF Protection in AJAX Forms — The Complete Fix

Thematisch verwandte Begriffe: CodeIgniter, CSRF, Protection, AJAX · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...