Autentikasi menjawab "kamu siapa", otorisasi menjawab "boleh gak kamu melakukan ini pada data ini". Di Laravel, aturan otorisasi untuk satu model ditulis di satu class bernama Policy. Bayangin kartu kamar hotel: resepsionis sudah tahu kamu siapa (autentikasi), tapi kartumu cuma membuka kamar 207 milikmu. Petugas kebersihan boleh masuk kamar mana pun untuk bersih-bersih, tapi gak boleh mengubah barangmu. Manajer punya kartu master yang membuka semua pintu.
🧪 Tebakan cepat: tombol Edit di ulasan Budi disembunyikan dari Ani dengan
@can. Ani mengirimPUT /reviews/7langsung lewat curl, dan controller-nya gak memanggilGate::authorize(). Apa yang terjadi: 403, 404, atau ulasan Budi berhasil diubah Ani? Tebak dulu, jawabannya ada di bagian "@can cuma menyembunyikan tombol".
Aturannya: ulasan buku di Perpustakaan
Anggota boleh menulis ulasan buku (model Review, tabel reviews dengan kolom user_id). Aturannya:
- Semua orang, termasuk tamu yang belum login, boleh membaca daftar ulasan.
- Cuma penulisnya yang boleh mengubah ulasan. Kata-katanya milik dia.
- Penulisnya atau pustakawan (
is_admin) boleh menghapus ulasan, misalnya yang berisi spam. - Kepala perpustakaan (
is_super_admin) boleh semuanya.
Siapa yang login sudah diurus middleware auth (lesson Middleware): tamu yang membuka halaman ber-auth diarahkan ke /login. Policy bekerja sesudahnya. Konsep role dan permission secara umum ada di lesson Role-based Access Control (RBAC) dan RBAC vs ABAC: Implementasi (track Auth).
Policy pertamamu
php artisan make:policy ReviewPolicy --model=Review
<?php
// app/Policies/ReviewPolicy.php
namespace App\Policies;
use App\Models\Review;
use App\Models\User;
class ReviewPolicy
{
public function before(User $user, string $ability): ?bool
{
if ($user->is_super_admin) {
return true; // kartu master: boleh semua
}
return null; // lanjut ke method aksinya
}
public function viewAny(?User $user): bool
{
return true; // tamu juga boleh membaca daftar
}
public function update(User $user, Review $review): bool
{
return $user->id === $review->user_id;
}
public function delete(User $user, Review $review): bool
{
return $user->id === $review->user_id || $user->is_admin;
}
}
make:policy ReviewPolicy --model=Reviewmembuat file diapp/Policieslengkap dengan methodviewAny,view,create,update,delete, dan lainnya. Hapus yang gak dipakai.- Laravel menemukannya sendiri: model
Reviewdiapp/Models, policyReviewPolicydiapp/Policies. Gak perlu didaftarkan. - Satu method satu aksi, mengembalikan
true(boleh) ataufalse(gak boleh). Nama aksinya ('update') adalah nama method-nya. $user->id === $review->user_id: cek kepemilikan, inti dari hampir semua policy.?User $userdiviewAnymembuat tamu ikut dicek. Tanpa tanda?, Laravel langsung menolak tamu tanpa memanggil method-nya.before()jalan paling dulu.trueartinya boleh tanpa cek lagi,falseartinya tolak,nullartinya lanjut ke method aksinya.before()cuma dipanggil kalau policy-nya punya method untuk aksi itu.
Begini hasilnya untuk ulasan #7 milik Budi (id 1):
| User | before() |
update |
delete |
|---|---|---|---|
| Budi, penulisnya | null |
1 === 1, boleh |
boleh |
| Ani (id 2), anggota lain | null |
2 === 1, ditolak |
ditolak |
| Rina, pustakawan | null |
ditolak | boleh, karena is_admin |
| Pak Dodi, kepala | true |
boleh, update() gak dipanggil |
boleh |
| Tamu | gak dipanggil | ditolak | ditolak |
Tiga tempat memakai policy
// 1. Di controller: lempar 403 kalau gak boleh
public function update(UpdateReviewRequest $request, Review $review)
{
Gate::authorize('update', $review); // memanggil ReviewPolicy::update()
$review->update($request->validated());
return redirect()->route('reviews.index');
}
{{-- 2. Di Blade: tampilkan tombol cuma kalau boleh --}}
@can('update', $review)
<a href="{{ route('reviews.edit', $review) }}">Edit</a>
@endcan
// 3. Di kode biasa: dapat true atau false, tanpa melempar apa-apa
if (Gate::allows('delete', $review)) {
// ...
}
Gate::authorize()melempar exception kalau jawabannyafalse, dan Laravel menjawab halaman403 | This action is unauthorized.(atau{"message":"This action is unauthorized."}untuk request JSON).$this->authorize('update', $review)sama artinya, tapi butuh traitAuthorizesRequests. Di Laravel 11 ke atas,app/Http/Controllers/Controller.phpkosong, jadi tambahkanuse AuthorizesRequests;di sana dulu.@can('update', $review)dan$request->user()->can('update', $review)cuma bertanya, gak menolak apa-apa.- Di route juga bisa:
->can('update', 'review')menjawab 403 sebelum controller dipanggil.
@can cuma menyembunyikan tombol
@can mengatur apa yang terlihat. Request tetap bisa dikirim tanpa tombol. Ani sudah login, jadi dia punya cookie session dan token CSRF sendiri, dan bisa mengirim PUT lewat curl, Postman, atau devtools browser. Kalau controller gak memanggil Gate::authorize(), PUT /reviews/7 dari Ani diterima, dan ulasan Budi berhasil diubah Ani. Itu jawaban tebakannya.
Jadi selalu cek di server (controller, route, atau FormRequest authorize()), dan pakai @can cuma supaya tampilannya rapi.
Gate untuk aturan tanpa model
Gak semua aturan menyangkut satu record. "Cuma pustakawan yang boleh membuka laporan bulanan" ditulis sebagai Gate di AppServiceProvider::boot():
Gate::define('view-reports', fn (User $user) => $user->is_admin);
// lalu di mana saja
Gate::authorize('view-reports');
Aturan tentang satu model (ulasan, peminjaman): Policy. Aturan tentang fitur: Gate.
⚠️ Jebakan yang sering ketemu
- Cuma
@can, tanpa cek di server. Tombolnya hilang, tapi URL-nya tetap terbuka untuk siapa pun yang tahu. $this->authorize()di Laravel 11 ke atas tanpa trait. HasilnyaCall to undefined method ...::authorize(). PakaiGate::authorize(), atau tambahkanuse AuthorizesRequests;di baseController.before()mengembalikanfalsepadahal maksudnya "lanjut".falsemenolak semua aksi, termasuk pemilik yang mengubah ulasannya sendiri. Untuk "lanjut", kembalikannull.- Nama gak cocok. Model
ReviewbutuhReviewPolicydiapp/Policies, dan aksi'update'memanggil methodupdate(). Salah satu huruf, aturannya gak pernah dipakai.
TL;DR: Policy berisi aturan "boleh gak" untuk satu model: php artisan make:policy ReviewPolicy --model=Review, satu method per aksi yang mengembalikan true atau false, biasanya dengan $user->id === $review->user_id. before() jalan duluan: true boleh semua, false tolak semua, null lanjut. Pakai Gate::authorize('update', $review) atau $this->authorize(...) (butuh trait AuthorizesRequests) di server untuk menjawab 403 | This action is unauthorized., @can di Blade untuk menyembunyikan tombol, dan Gate::allows() untuk sekadar bertanya. @can saja gak melindungi apa-apa: request langsung tetap lolos kalau server gak mengecek.