Mengamankan komponen Livewire: cek otorisasi dengan policy dan authorize() di setiap action, conditional rendering dengan @can, serta penanganan CSRF/XSS, sanitization, dan binding protection untuk mencegah manipulasi property dari client.

Sejauh ini kita membangun fitur tanpa memikirkan siapa yang berhak menjalankannya. Di aplikasi nyata — terutama admin panel dan ERP — pertanyaan "siapa yang boleh melakukan ini?" sama pentingnya dengan "bagaimana melakukannya?". Komponen Livewire yang interaktif membuat permukaan serangan berbeda dari halaman statis, jadi episode ini wajib dipahami sebelum produksi.
Poin penting yang sering diabaikan: menampilkan tombol bukan berarti user berhak menjalankan action-nya. Livewire mengirim request langsung ke server — user yang cerdas bisa memicu wire:click="delete(1)" tanpa melihat tombolnya. Karena itu semua otorisasi harus diperiksa di server, bukan hanya di tampilan.
Laravel menyediakan policy — class yang memusatkan aturan akses per model. Buat dengan artisan:
php artisan make:policy PostPolicy --model=Postpublic function update(User $user, Post $post)
{
return $user->isAdmin() || $user->id === $post->user_id;
}
public function delete(User $user, Post $post)
{
return $user->isAdmin();
}Lalu di dalam action komponen, panggil authorize():
use Livewire\Component;
public function update()
{
$this->authorize('update', $this->post);
// hanya user yang diizinkan sampai di sini...
$this->form->update($this->post);
}
public function delete(int $postId)
{
$post = Post::findOrFail($postId);
$this->authorize('delete', $post);
$post->delete();
}Jika tidak berhak, authorize() melempar AuthorizationException (403) — dan action batal. Inilah lapisan pengaman utama: apapun yang terjadi di UI, server tetap menjadi hakim terakhir.
Warning
Jangan pernah mengandalkan tombol yang "tidak ditampilkan" sebagai pengaman. wire:click hanyalah permintaan HTTP — setiap user dengan akses ke halaman bisa memalsukannya. Pola yang benar: tampilkan tombol secara kondisional untuk UX, dan authorize() di action untuk keamanan.
Untuk UX, sembunyikan kontrol yang tidak berhak dipakai user:
<div>
<h1>{{ $post->title }}</h1>
@can('update', $post)
<button wire:click="edit">Edit</button>
@endcan
@can('delete', $post)
<button wire:click="delete" wire:confirm="Yakin hapus?">Hapus</button>
@endcan
</div>Di dalam komponen Livewire, @can tetap bekerja normal karena user tersedia dari auth. Ingat: ini hanya lapisan tampilan — lapisan server (authorize) tetap wajib.
Livewire mengirim request via fetch dengan header khusus dan memvalidasi CSRF token Laravel di setiap request. Selama kalian tidak menonaktifkan middleware VerifyCsrfToken dan @livewireScripts tetap ada di layout, perlindungan CSRF berjalan otomatis:
@livewireScripts ke header request.livewire/update memvalidasinya seperti route biasa.Yang perlu kalian jaga: jangan buang @livewireScripts dari halaman hanya karena ingin menghemat beberapa kilobyte. Ia adalah bagian dari sistem keamanan, bukan sekadar pemuat JavaScript.
Blade secara default melakukan escaping pada output dengan {{ }}. Kalian yang menonaktifkannya:
<p>{{ $post->title }}</p><p>{!! $post->body !!}</p>Aturan emasnya:
{{ }} untuk data yang berasal dari user — sudah di-escape.{!! !!} hanya untuk konten yang kalian kontrol penuh (misalnya hasil Markdown yang sudah di-sanitize), atau konten dari user yang sudah melewati filter ketat.Untuk konten user berupa HTML, sanitize dulu di server (misalnya dengan htmlspecialchars, atau library sanitizer seperti HTMLPurifier) — jangan pernah menampilkan HTML mentah dari user langsung.
Bersihkan input sebelum diproses, bukan sesudah. Contoh: memastikan data string, membuang tag HTML, dan menetapkan default:
public function saveComment()
{
$this->validate([
'body' => ['required', 'string', 'max:500'],
]);
Comment::create([
'post_id' => $this->postId,
'body' => strip_tags($this->body),
]);
}Aturan validasi (string, max) mencegah tipe data tak terduga; strip_tags membuang sisa markup dari field teks biasa. Sesuaikan sanitasi dengan konteks: field teks biasa = buang tag; editor kaya = sanitize whitelist HTML.
Ini fitur keamanan spesifik Livewire yang wajib dipahami: karena public property di-isi dari payload client, secara teori user bisa memodifikasi property apa pun yang ter-serialisasi. Binding protection mencegah hal ini:
use Livewire\Attributes\Locked;
class PostEditor extends Component
{
#[Locked]
public int $postId;
public string $title = '';
}#[Locked] membuat property tidak bisa diubah dari client — nilainya hanya bisa di-set dari server (mount()). Contoh di atas: user tidak bisa mengubah postId melalui request palsu untuk menyamar sebagai mengedit post lain.
Ada juga #[Session] untuk property yang dipertahankan di session server (tidak dikirim ke client sama sekali) — ideal untuk data sensitif seperti ID user aktif.
authorize() di action.{!! !!} pada input user: XSS. Escape dengan {{ }} atau sanitize ketat.#[Locked]: user bisa mengganti ID dan menyentuh data orang lain.mount(): data yang dimuat di awal pun harus dicek izinnya.Inti yang harus dibawa pulang:
authorize('ability', $model) di setiap action yang mengubah data.@can hanya untuk UX; bukan pengaman.@livewireScripts dan middleware tetap aktif.{{ }}; {!! !!} hanya untuk konten yang kalian kontrol.#[Locked]/#[Session] untuk property yang tidak boleh diubah client.Di episode 14 selanjutnya kita akan membahas lazy loading & wire:init — menunda render komponen berat dengan lazy/#[Lazy], placeholder states, dan strategi memotong payload halaman untuk waktu muat yang lebih cepat. Sampai jumpa di episode 14!