Melindungi aplikasi dari data input yang tidak valid: validation rules dan custom rules, menampilkan error dengan benar, lalu mempertahankan diri dari CSRF, XSS, dan input berbahaya dengan filter dan escaping bawaan.

Setelah di episode 7 skema blog kalian rapi dengan migrations dan seeding, sekarang tibalah saatnya mengamankan pintu masuk utama aplikasi: form. Setiap data yang dikirim user melewati form, dan tanpa validasi serta perlindungan keamanan, satu request jahat bisa merusak data, menjalankan script di browser orang lain, atau mengirim query aneh ke database.
Di episode 8 ini kita membahas form validation dan security dasar — dua hal yang di CodeIgniter 4 saling terkait: validation menolak data tidak valid, sedangkan CSRF/XSS protection memblokir serangan sebelum sampai ke aplikasi.
CodeIgniter menyediakan sistem validasi via $this->validate() di controller:
public function create(): \CodeIgniter\HTTP\RedirectResponse | string
{
$rules = [
'title' => 'required|min_length[3]|max_length[255]',
'slug' => 'required|alpha_dash|is_unique[posts.slug]',
'status' => 'required|in_list[draft,published]',
];
if (! $this->validate($rules)) {
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
}
$postModel = new \App\Models\PostModel();
$postModel->save([
'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'),
'body' => $this->request->getPost('body'),
'status' => $this->request->getPost('status'),
]);
return redirect()->to('/posts')->with('success', 'Post berhasil dibuat');
}Rule digabung dengan |. Yang paling sering dipakai:
| Rule | Fungsi |
|---|---|
required | Nilai tidak boleh kosong |
min_length[n] / max_length[n] | Panjang string |
valid_email | Format email valid |
alpha_dash | Huruf, angka, underscore, dash |
is_unique[posts.slug] | Nilai unik di kolom tabel |
in_list[a,b] | Harus salah satu dari daftar |
matches[field] | Cocok dengan field lain (konfirmasi password) |
withInput() mengembalikan data lama ke form sehingga user tidak perlu mengetik ulang, dan with('errors', ...) membawa array error ke session flash — keduanya kita ambil di view.
<?= $this->extend('layouts/default') ?>
<?= $this->section('content') ?>
<?php if (session('errors')): ?>
<div class="alert alert-danger">
<ul>
<?php foreach (session('errors') as $error): ?>
<li><?= esc($error) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?= form_open('posts/store') ?>
<?= csrf_field() ?>
<div>
<label for="title">Judul</label>
<input type="text" name="title" id="title"
value="<?= esc(old('title')) ?>">
</div>
<button type="submit">Simpan</button>
<?= form_close() ?>
<?= $this->endSection() ?>old('title') mengembalikan nilai yang dikirim sebelumnya (dari withInput()), dan csrf_field() menyisipkan token CSRF — kita bahas sebentar lagi.
Tip
redirect()->back() kembali ke halaman asal, tapi jika ada masalah dengan referer, gunakan redirect eksplisit: redirect()->to('/posts/create'). Untuk form yang di-submit via AJAX, kembalikan JSON error dan render di client.
Rule bawaan tidak selalu cukup. Contoh: validasi slug yang sudah dipakai. Dua cara membuat rule custom:
Cara 1 — callable anonim:
$rules = [
'slug' => static function (string $value): bool {
return (bool) preg_match('/^[a-z0-9-]+$/', $value);
},
];Cara 2 — class rules yang terdaftar:
Buat app/Validation/MyRules.php dan daftarkan di app/Config/Validation.php $ruleSets:
<?php
namespace App\Validation;
class MyRules
{
public function slug_valid(string $str): bool
{
return (bool) preg_match('/^[a-z0-9-]+$/', $str);
}
}Lalu gunakan sebagai slug_valid di rule:
$rules = ['slug' => 'required|slug_valid'];CSRF (Cross-Site Request Forgery) adalah serangan di mana penyerang mengelabui browser korban untuk mengirim request (misal: ganti password) ke aplikasi yang sedang login. Pertahanannya: setiap form membawa token unik yang dicek server.
Di CodeIgniter 4, filter CSRF sudah terdaftar global untuk semua route POST. Aktifkan di app/Config/Filters.php:
public $globals = [
'before' => [
'csrf',
'honeypot',
],
];Lalu setiap form harus menyertakan token — itulah csrf_field() yang kita pakai di view. Saat submit, server membandingkan token; jika tidak cocok, request ditolak dengan 403.
<?= form_open('posts/store') ?>
<?= csrf_field() ?>
...
<?= form_close() ?>Warning
Jika kalian menguji form dengan curl/Postman dan mendapat error CSRF, itu normal — CSRF token adalah proteksi nyata, bukan gangguan. Untuk API endpoint (episode 13), filter CSRF sengaja dikecualikan karena digantikan API key/token auth.
XSS (Cross-Site Scripting) terjadi saat input user dirender sebagai kode oleh browser korban. Dua lapis pertahanan:
esc() mengubah karakter berbahaya (<, >, &, ", ') menjadi entitas HTML:
<h1><?= esc($post['title']) ?></h1>
<p><?= esc($post['body']) ?></p>Untuk konten yang memang perlu menyimpan HTML (misalnya editor rich text), gunakan sanitizer. CodeIgniter menyediakan HTMLPurifier melalui library:
composer require ezyang/htmlpurifieruse HTMLPurifier;
use HTMLPurifier_Config;
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$cleanBody = $purifier->purify($this->request->getPost('body'));Aturan sederhananya: escape semua output secara default; jika memang harus mengizinkan HTML, sanitasi dengan whitelist tag di input.
Note
Aturan emasnya: validasi menolak data buruk di input, escaping memastikan data apapun aman di output. Keduanya wajib — validasi tanpa escaping bisa bocor lewat data lama, escaping tanpa validasi membiarkan data sampah masuk database.
$this->validate($rules) di controller, error dibawa dengan withInput() dan with('errors').Validation.php.csrf_field().esc(), sanitasi HTML dengan HTMLPurifier jika memang dibutuhkan.old() dan withInput() membuat form tetap ramah saat validasi gagal.Inti yang harus dibawa pulang:
is_unique, in_list, dan alpha_dash mencegah data aneh masuk database.Di episode 9 selanjutnya kita akan membahas sessions, flash data & cookies — menyimpan state antar request, memanfaatkan flashdata untuk pesan sekali tampil, mengelola cookie, dan praktik login/logout sederhana. Persiapan untuk autentikasi penuh di episode 12!