Validasi input berlapis di CodeIgniter 4 diperlukan jika API Anda tidak hanya ingin memeriksa format data, tetapi juga ingin menolak request yang berisiko disalahgunakan. Banyak API terlihat aman karena sudah memakai rule validasi dasar, padahal masih rentan terhadap overposting, payload terlalu besar, header yang tidak sesuai, field liar yang tidak diharapkan, dan input ambigu yang menyulitkan logika bisnis.
Pendekatan yang lebih aman adalah memecah pemeriksaan menjadi beberapa lapisan: validasi header, pembatasan ukuran body, parsing request yang ketat, normalisasi nilai, whitelist field, validasi rule bisnis, dan respons error yang konsisten. Dengan begitu, request yang tidak masuk akal bisa ditolak lebih awal sebelum menyentuh model atau database.
Mengapa validasi dasar saja tidak cukup
Rule seperti required, valid_email, atau max_length memang penting, tetapi itu hanya menjawab satu pertanyaan: apakah nilai ini tampak valid? Dalam API nyata, masih ada pertanyaan lain yang sama pentingnya:
- Apakah request memakai
Content-Typeyang benar? - Apakah body terlalu besar untuk endpoint ini?
- Apakah ada field tambahan yang tidak boleh dikirim klien?
- Apakah nama field mirip tetapi ambigu, misalnya
userIddanuser_iddikirim bersamaan? - Apakah nilai perlu dinormalisasi dulu sebelum divalidasi?
- Apakah error response konsisten agar mudah ditangani klien dan mudah dilog?
Jika hanya mengandalkan validasi dasar, beberapa risiko yang sering muncul adalah:
- Overposting: klien mengirim field sensitif seperti
role,status, atauis_adminwalaupun endpoint tidak seharusnya menerima itu. - Bypass logika bisnis: nilai dengan spasi, huruf besar-kecil, atau format alternatif lolos meskipun secara semantik tidak diinginkan.
- DoS ringan: body JSON sangat besar memaksa server memproses request yang seharusnya bisa ditolak lebih awal.
- Input ambigu: dua field berbeda merepresentasikan data yang sama dan menimbulkan hasil tak konsisten.
- Header abuse: request tanpa header penting atau dengan tipe konten salah tetap diproses.
Desain validasi input berlapis di CodeIgniter 4
Urutan berikut praktis dipakai untuk banyak endpoint API:
- Filter awal: cek metode, header penting, dan batas ukuran payload.
- Parsing request: hanya terima body dalam format yang diharapkan.
- Whitelist field: buang atau tolak field yang tidak diizinkan.
- Normalisasi data: trim, ubah casing, rapikan format numerik/string.
- Validasi rule: gunakan Validation untuk format, panjang, dan daftar nilai yang valid.
- Penolakan request ambigu: deteksi kombinasi field yang membingungkan atau konflik.
- Logging seperlunya: catat konteks teknis tanpa membocorkan payload sensitif.
Prinsip pentingnya: tolak seawal mungkin, proses sesedikit mungkin.
Implementasi Request dan Controller yang ketat
1. Endpoint contoh
Misalkan kita punya endpoint POST /api/users untuk membuat pengguna. Endpoint ini hanya menerima field berikut:
nameemailphone
Field seperti role, status, atau is_admin harus ditolak.
2. Controller dengan parsing JSON, whitelist, normalisasi, dan validasi
<?php
namespace App\Controllers\Api;
use CodeIgniter\RESTful\ResourceController;
class Users extends ResourceController
{
protected $format = 'json';
public function create()
{
if (! $this->request->is('post')) {
return $this->fail('Metode request tidak diizinkan.', 405);
}
$contentType = $this->request->getHeaderLine('Content-Type');
if ($contentType === '' || stripos($contentType, 'application/json') !== 0) {
return $this->respondValidationError([
'content_type' => 'Content-Type harus application/json.'
], 415);
}
$payload = $this->request->getJSON(true);
if (! is_array($payload)) {
return $this->respondValidationError([
'body' => 'Body JSON tidak valid atau tidak dapat diparsing.'
], 400);
}
$allowedFields = ['name', 'email', 'phone'];
$incomingFields = array_keys($payload);
$unknownFields = array_diff($incomingFields, $allowedFields);
if (! empty($unknownFields)) {
return $this->respondValidationError([
'fields' => 'Field tidak diizinkan: ' . implode(', ', $unknownFields)
], 422);
}
if (array_key_exists('user_id', $payload) && array_key_exists('userId', $payload)) {
return $this->respondValidationError([
'fields' => 'Request ambigu: gunakan salah satu field identitas, bukan keduanya.'
], 422);
}
$data = [
'name' => isset($payload['name']) ? trim($payload['name']) : null,
'email' => isset($payload['email']) ? strtolower(trim($payload['email'])) : null,
'phone' => isset($payload['phone']) ? preg_replace('/\s+/', '', trim($payload['phone'])) : null,
];
$rules = [
'name' => 'required|min_length[3]|max_length[100]',
'email' => 'required|valid_email|max_length[190]',
'phone' => 'permit_empty|regex_match[/^\+?[0-9]{8,15}$/]'
];
if (! $this->validateData($data, $rules)) {
return $this->respondValidationError($this->validator->getErrors(), 422);
}
// Simpan hanya field yang memang diizinkan.
// $userId = $this->userModel->insert($data, true);
return $this->respondCreated([
'message' => 'User berhasil dibuat.',
'data' => $data
]);
}
private function respondValidationError(array $errors, int $status)
{
return $this->response->setStatusCode($status)->setJSON([
'error' => [
'code' => $status,
'message' => 'Request tidak valid.',
'details' => $errors,
]
]);
}
}
Beberapa hal penting dari contoh di atas:
- Header diperiksa dulu, sebelum body diproses lebih jauh.
- JSON harus benar-benar array, bukan string atau struktur lain yang tidak sesuai ekspektasi endpoint.
- Whitelist field dilakukan eksplisit, bukan sekadar mengambil semua input lalu berharap model akan aman.
- Normalisasi dilakukan sebelum validasi, sehingga rule memeriksa data yang sudah seragam.
- Request ambigu ditolak agar aplikasi tidak punya dua sumber kebenaran untuk satu makna data.
Filter untuk validasi header dan batas ukuran payload
Sebagian pemeriksaan lebih tepat ditempatkan di Filter, terutama yang bersifat umum lintas endpoint. Ini membuat controller tetap fokus pada logika data.
1. Filter pembatasan ukuran body
CodeIgniter 4 tidak perlu menunggu validasi field jika payload sudah jelas terlalu besar. Anda bisa membaca header Content-Length sebagai pemeriksaan awal. Ini bukan satu-satunya perlindungan, tetapi berguna untuk menolak request berukuran tidak masuk akal.
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class MaxPayloadFilter implements FilterInterface
{
private int $maxBytes = 10240; // 10 KB, sesuaikan per kebutuhan endpoint
public function before(RequestInterface $request, $arguments = null)
{
$contentLength = $request->getHeaderLine('Content-Length');
if ($contentLength !== '' && ctype_digit($contentLength)) {
if ((int) $contentLength > $this->maxBytes) {
return service('response')
->setStatusCode(413)
->setJSON([
'error' => [
'code' => 413,
'message' => 'Payload terlalu besar.',
'details' => [
'payload' => 'Ukuran request melebihi batas endpoint.'
]
]
]);
}
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
Catatan: pemeriksaan berdasarkan Content-Length berguna sebagai filter awal, tetapi tidak boleh dianggap perlindungan tunggal. Di level web server atau reverse proxy, pembatasan ukuran request tetap disarankan karena lebih efisien daripada menolak setelah request masuk ke aplikasi.
2. Filter validasi header penting
<?php
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class ApiRequestGuardFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$contentType = $request->getHeaderLine('Content-Type');
$accept = $request->getHeaderLine('Accept');
if ($request->is('post') || $request->is('put') || $request->is('patch')) {
if ($contentType === '' || stripos($contentType, 'application/json') !== 0) {
return service('response')
->setStatusCode(415)
->setJSON([
'error' => [
'code' => 415,
'message' => 'Content-Type tidak didukung.',
'details' => [
'content_type' => 'Gunakan application/json.'
]
]
]);
}
}
if ($accept !== '' && stripos($accept, 'application/json') === false && stripos($accept, '*/*') === false) {
return service('response')
->setStatusCode(406)
->setJSON([
'error' => [
'code' => 406,
'message' => 'Accept header tidak didukung.',
'details' => [
'accept' => 'Endpoint ini hanya merespons JSON.'
]
]
]);
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
3. Registrasi filter
Daftarkan filter di konfigurasi filter aplikasi Anda, lalu terapkan ke grup route API yang relevan.
<?php
// app/Config/Filters.php
public array $aliases = [
'apiRequestGuard' => \App\Filters\ApiRequestGuardFilter::class,
'maxPayload' => \App\Filters\MaxPayloadFilter::class,
];
<?php
// app/Config/Routes.php
$routes->group('api', ['filter' => 'apiRequestGuard,maxPayload'], static function ($routes) {
$routes->post('users', 'Api\Users::create');
});
Jika kebutuhan berbeda per endpoint, Anda bisa membuat filter khusus atau memisahkan route group. Misalnya endpoint upload file tentu memerlukan aturan yang berbeda dari endpoint JSON sederhana.
Whitelist field dan normalisasi data
Kenapa whitelist lebih aman daripada sekadar mengabaikan field asing
Ada dua pendekatan umum:
- Membuang field asing secara diam-diam
- Menolak request jika ada field asing
Untuk API yang ingin jelas dan defensif, pendekatan kedua biasanya lebih aman. Alasannya:
- Klien segera tahu ada kontrak request yang dilanggar.
- Mencegah bug tersembunyi karena klien merasa field tambahan ikut diproses.
- Mengurangi risiko perubahan perilaku ketika di masa depan field itu kebetulan menjadi valid.
Jika Anda memang perlu kompatibilitas longgar dengan klien lama, field asing bisa diabaikan. Namun keputusan itu sebaiknya disengaja, bukan efek samping.
Normalisasi bukan sanitasi HTML
Dalam konteks API, normalisasi berarti merapikan bentuk data agar validasi dan logika bisnis konsisten. Contoh:
trim()untuk menghapus spasi awal/akhirstrtolower()untuk email- menghapus spasi pada nomor telepon
- mengubah string kosong menjadi
nulljika memang masuk akal
Ini berbeda dengan sanitasi output HTML. Jangan mencampur keduanya. Input API tidak perlu langsung di-escape untuk HTML saat masuk; escaping seharusnya dilakukan saat data ditampilkan ke konteks output tertentu.
Contoh helper normalisasi sederhana
<?php
if (! function_exists('normalize_nullable_string')) {
function normalize_nullable_string($value): ?string
{
if ($value === null) {
return null;
}
$value = trim((string) $value);
return $value === '' ? null : $value;
}
}
Dengan helper kecil seperti ini, Anda bisa menjaga agar normalisasi konsisten di banyak controller atau service.
Memakai Validation secara tepat di CI4
Lapisan Validation di CodeIgniter 4 tetap menjadi inti pemeriksaan format data. Namun sebaiknya ia dipakai setelah request lolos pemeriksaan dasar lain.
Contoh rule terpusat
Jika endpoint digunakan berulang atau memiliki banyak field, simpan rule dalam konfigurasi validasi agar lebih mudah dirawat.
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Validation extends BaseConfig
{
public array $userCreate = [
'name' => 'required|min_length[3]|max_length[100]',
'email' => 'required|valid_email|max_length[190]',
'phone' => 'permit_empty|regex_match[/^\+?[0-9]{8,15}$/]'
];
public array $userCreate_errors = [
'name' => [
'required' => 'Nama wajib diisi.',
],
'email' => [
'valid_email' => 'Format email tidak valid.',
],
];
}
Lalu di controller:
<?php
if (! $this->validateData($data, config('Validation')->userCreate, config('Validation')->userCreate_errors ?? [])) {
return $this->respondValidationError($this->validator->getErrors(), 422);
}
Jika pola pemanggilan validasi di proyek Anda berbeda, inti praktiknya tetap sama: pusatkan rule penting, hindari duplikasi, dan pertahankan pesan error yang konsisten.
Kesalahan umum saat memakai Validation
- Memvalidasi data mentah tanpa normalisasi, sehingga email dengan spasi gagal atau lolos tidak konsisten.
- Menganggap validasi sama dengan whitelist. Rule validasi tidak otomatis menolak field ekstra.
- Menaruh logika bisnis kompleks di rule sederhana. Untuk kasus lebih rumit, gunakan service atau validasi kustom yang jelas.
- Memproses request campuran dari
getPost(),getJSON(), dan query string sekaligus tanpa aturan yang tegas.
Menolak request ambigu dan input yang tidak masuk akal
Tidak semua abuse berbentuk karakter berbahaya. Sering kali masalah muncul dari request yang secara sintaks valid tetapi maknanya membingungkan.
Contoh request ambigu
emaildikirim sebagai string kosong padahal klien juga mengirim field identitas lain yang tidak semestinya dipakai bersama.user_iddanuserIdmuncul bersamaan.pricedandiscounted_pricememiliki kombinasi yang bertentangan dengan aturan bisnis.- Field yang sama maknanya hadir di body dan query string dengan nilai berbeda.
Prinsipnya: jika endpoint hanya mendukung satu cara menyatakan data, maka cara lain harus ditolak. Ini mengurangi perilaku tak terduga dan mempermudah debugging.
Jika API Anda diam-diam memilih salah satu dari dua nilai yang konflik, Anda sedang menciptakan bug yang sulit dilacak. Lebih baik gagal cepat dengan pesan yang jelas.
Error response yang konsisten
Respons error yang konsisten membuat klien lebih mudah menangani kegagalan, sekaligus mempermudah observabilitas. Hindari mencampur format error antar-controller.
Struktur respons yang disarankan
{
"error": {
"code": 422,
"message": "Request tidak valid.",
"details": {
"email": "Format email tidak valid."
}
}
}
Beberapa panduan praktis:
- 400 untuk body rusak atau tidak bisa diparsing.
- 406 jika
Accepttidak didukung. - 413 untuk payload terlalu besar.
- 415 untuk
Content-Typeyang tidak sesuai. - 422 untuk field validasi gagal atau field asing/ambigu.
Tidak semua tim memakai pembagian status yang persis sama, tetapi konsistensi lebih penting daripada variasi yang membingungkan.
Logging seperlunya tanpa membocorkan data sensitif
Logging tetap penting untuk investigasi abuse, tetapi jangan menulis seluruh payload mentah jika berpotensi berisi email, nomor telepon, token, atau data rahasia lain.
Apa yang layak dicatat
- endpoint dan metode HTTP
- status code
- jenis kegagalan, misalnya
invalid_content_typeatauunknown_fields - daftar nama field yang bermasalah, bukan nilainya
- IP atau identitas klien jika kebijakan privasi mengizinkan
- ukuran payload, bukan isi payload lengkap
Contoh logging aman
<?php
log_message('warning', 'API request rejected: {reason}; endpoint={endpoint}; fields={fields}; content_length={length}', [
'reason' => 'unknown_fields',
'endpoint' => '/api/users',
'fields' => implode(',', $unknownFields),
'length' => $this->request->getHeaderLine('Content-Length'),
]);
Hindari log seperti ini:
- seluruh body JSON mentah
- token otorisasi
- password atau secret lain
- stack trace mentah ke klien produksi
Konfigurasi tambahan yang relevan
Selain validasi di level aplikasi, pembatasan di luar aplikasi juga penting. Misalnya:
- batas ukuran body di web server atau reverse proxy
- rate limiting di gateway, proxy, atau lapisan aplikasi jika diperlukan
- timeout request yang masuk akal
Alasannya sederhana: menolak request berbahaya sedekat mungkin dengan tepi sistem biasanya lebih hemat sumber daya daripada menolaknya setelah aplikasi penuh memproses body.
Untuk model, tetap gunakan pengamanan field yang boleh ditulis. Walaupun artikel ini fokus pada request validation, proteksi di model membantu mengurangi dampak jika ada jalur input yang lupa dipagari.
Menguji input berbahaya, overposting, dan payload besar
Validasi berlapis harus diuji dengan request yang memang salah, bukan hanya skenario sukses.
1. Uji overposting
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name":"Budi","email":"[email protected]","phone":"08123456789","role":"admin"}'
Ekspektasi: API mengembalikan 422 dan menyebut role sebagai field yang tidak diizinkan.
2. Uji JSON rusak
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name":"Budi","email":"[email protected]",}'
Ekspektasi: API mengembalikan 400 karena body tidak dapat diparsing.
3. Uji header salah
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: text/plain" \
-d 'name=Budi'
Ekspektasi: API mengembalikan 415.
4. Uji payload besar
Anda bisa mengirim body yang ukurannya melebihi batas filter endpoint. Ekspektasinya adalah 413. Jika respons tidak sesuai, periksa apakah pembatasan di web server lebih dulu memotong request, karena itu bisa mengubah titik kegagalan yang terlihat dari sisi aplikasi.
5. Uji nilai yang tampak valid tetapi tidak masuk akal
- email dengan spasi di awal/akhir
- nomor telepon dengan banyak spasi internal
- field kosong berupa string kosong
- field duplikat makna dengan nama berbeda
Tujuan pengujian ini adalah memastikan normalisasi dan penolakan ambiguitas bekerja sebagaimana desainnya.
6. Uji otomatis
Jika proyek Anda sudah memiliki pengujian HTTP, buat test untuk setiap lapisan: header salah, body invalid, field asing, validasi format gagal, dan request sukses. Dengan begitu, perubahan kecil pada controller atau filter tidak diam-diam melonggarkan kontrak API.
Trade-off dan batasan pendekatan ini
- Lebih banyak kode awal, tetapi lebih mudah dirawat ketika API tumbuh.
- Whitelist ketat bisa memutus klien lama jika kontrak API sebelumnya longgar.
- Normalisasi berlebihan bisa mengubah makna data jika dilakukan tanpa aturan bisnis yang jelas.
- Pembatasan payload di aplikasi bukan pengganti proteksi di level infrastruktur.
Karena itu, terapkan validasi berlapis sesuai konteks endpoint. Endpoint publik dengan risiko abuse tinggi biasanya layak lebih ketat dibanding endpoint internal yang sangat terkontrol.
Penutup
CodeIgniter 4: validasi input berlapis untuk cegah abuse API bukan sekadar menambah banyak rule validasi. Intinya adalah memisahkan tanggung jawab: filter untuk request-level guard, controller/service untuk parsing dan normalisasi, validation untuk format data, serta model untuk membatasi field yang boleh ditulis.
Jika Anda hanya mengandalkan validasi dasar, API masih bisa menerima request yang technically valid tetapi secara operasional berbahaya atau membingungkan. Dengan whitelist field, validasi header, batas payload, normalisasi, penolakan request ambigu, error response konsisten, dan logging aman, API Anda akan jauh lebih tahan terhadap abuse yang sering terjadi pada lapisan input.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!