Input Validation & Schema — Security

Input Validation untuk Keamanan Validasi input bukan hanya tentang UX — ini adalah garis pertahanan pertama terhadap serangan. Semua input dari luar (form…

Validasi input berarti mengecek setiap data yang datang dari luar terhadap aturan yang jelas, sebelum data itu dipakai. Bayangin teller bank yang menerima formulir setoran: nama harus terisi, nomor rekening harus 10 digit, jumlahnya harus angka positif. Formulir yang salah dikembalikan dengan catatan di kolom yang salah, gak diperbaiki diam-diam. Dan walaupun aplikasi bank di HP-mu sudah mengecek formulir itu, teller tetap mengecek ulang, karena orang bisa datang membawa formulir buatan sendiri.

🧪 Tebakan cepat: form daftarmu menolak umur di bawah 13 lewat validasi di browser. Seseorang mengirim {"age": 10} langsung ke API pakai curl, melewati form. Server yang gak memvalidasi menerima data itu atau gak? Tebak dulu, jawabannya ada di bagian "Client untuk UX, server untuk keamanan".

Aturan untuk tiap field

Di Lab dan di latihan, form daftar akun punya tiga field. Ini aturannya:

Field Aturan
name wajib ada, berupa teks, 2 sampai 50 karakter
email wajib ada, berupa teks, mengandung @ dan .
age wajib ada, berupa angka bulat, 13 sampai 120

Tiga prinsip di baliknya:

Validasi manual dengan JavaScript

function validateInput(data) {
  const errors = []

  if (typeof data.name !== 'string' || data.name.length < 2 || data.name.length > 50) {
    errors.push('nama harus 2 sampai 50 karakter')
  }
  if (typeof data.email !== 'string' || !data.email.includes('@') || !data.email.includes('.')) {
    errors.push('email harus mengandung @ dan .')
  }
  if (!Number.isInteger(data.age) || data.age < 13 || data.age > 120) {
    errors.push('umur harus angka bulat 13 sampai 120')
  }

  return { valid: errors.length === 0, errors }
}

Ini hasilnya untuk data yang sama dengan di Lab:

Input Hasil
{ name: 'Budi', email: '[email protected]', age: 25 } { valid: true, errors: [] }
{ name: '', email: 'invalid', age: 10 } valid: false, 3 error: nama, email, umur
{ name: 'A', email: '[email protected]', age: 25 } valid: false, nama terlalu pendek
{ name: 'Budi', email: '[email protected]' } valid: false, umur gak ada

Schema dengan Zod

Menulis if untuk tiap aturan cepat jadi panjang. Schema adalah cetakan aturan yang ditulis sekali, lalu dipakai untuk mengecek data. Zod adalah library schema yang populer di JavaScript dan TypeScript:

import { z } from 'zod'

const UserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.email(),
  age: z.number().int().min(13).max(120),
})

app.post('/api/users', (req, res) => {
  const result = UserSchema.safeParse(req.body)
  if (!result.success) {
    return res.status(422).json({ errors: z.flattenError(result.error).fieldErrors })
  }
  db.users.insert(result.data)
  res.status(201).json(result.data)
})

Untuk { name: '', email: 'invalid', age: 10 }, Zod 4 menjawab dengan pesan ini, persis seperti yang tampil di Lab:

{
  "errors": {
    "name": ["Too small: expected string to have >=2 characters"],
    "email": ["Invalid email address"],
    "age": ["Too small: expected number to be >=13"]
  }
}

Di Laravel

class StoreUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'min:2', 'max:50'],
            'email' => 'required|email|max:255',
            'age' => ['required', 'integer', 'min:13', 'max:120'],
        ];
    }
}

Client untuk UX, server untuk keamanan

Validasi di browser bisa dilewati: lewat DevTools, curl, Postman, atau proxy seperti Burp Suite. Jadi server yang gak memvalidasi menerima {"age": 10}, dan data salah itu masuk database. Itu jawaban tebakannya.

Yang sering lupa divalidasi

⚠️ Jebakan yang sering ketemu

TL;DR: Validasi setiap input dari luar dengan aturan allowlist, tolak yang salah, dan lakukan seawal mungkin. Fungsi manual mengumpulkan error ke array dan mengembalikan { valid: errors.length === 0, errors }: nama 2 sampai 50 karakter, email mengandung @ dan ., umur angka bulat 13 sampai 120, dan field yang gak ada ikut ditolak. Schema seperti Zod menulis aturan sekali (z.number().int().min(13).max(120), atau .min(0) untuk "gak negatif") dan safeParse memberi error per field untuk dijawab dengan 422. Di Laravel, Form Request dengan rule seperti required|email|max:255. Validasi client untuk UX, validasi server untuk keamanan: lakukan keduanya, karena client bisa dilewati.

Yang akan kamu pelajari