Otorisasi — gate & policy
Autentikasi menjawab siapa kamu; otorisasi menjawab apa yang boleh kamu sentuh. Menyebarkan jawabannya ke dalam if di seluruh controller adalah cara paling andal untuk melewatkan satu tempat.
Intisari
- Gate untuk izin tanpa objek ("boleh masuk dasbor admin"); policy untuk izin atas sebuah model.
- Policy ditemukan otomatis:
Produk→ProdukPolicy, satu method per aksi. - Panggil dari controller (
authorize), rute (can), atau atribut#[Authorize]Laravel 13. Gate::before()untuk superadmin — hati-hati, ia mengalahkan semua policy.@candi Blade hanya menyembunyikan tombol. Ia bukan pengaman.
Policy: satu kelas per model
php artisan make:policy ArtikelPolicy --model=Artikel
class ArtikelPolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function view(?User $user, Artikel $artikel): bool
{
// ?User — pengunjung anonim juga boleh melihat artikel terbit
return $artikel->terbit_pada?->isPast() ?? false
|| $user?->id === $artikel->penulis_id;
}
public function create(User $user): bool
{
return $user->peran->bisaMenulis();
}
public function update(User $user, Artikel $artikel): bool
{
return $user->id === $artikel->penulis_id
|| $user->peran === Peran::Editor;
}
public function delete(User $user, Artikel $artikel): Response
{
// Kembalikan Response supaya alasannya ikut terkirim
return $user->peran === Peran::Editor
? Response::allow()
: Response::deny('Hanya editor yang boleh menghapus artikel.');
}
}
Tipe ?User menentukan perilaku untuk tamu. Kalau parameternya User
(tanpa tanda tanya), Laravel otomatis menolak pengunjung yang belum login tanpa menjalankan method-nya sama
sekali. Untuk halaman publik yang punya aturan berbeda bagi pemiliknya, tulis ?User dan tangani
null secara eksplisit.
Memakainya
// 1. Di controller — melempar 403 kalau ditolak
public function update(Request $request, Artikel $artikel)
{
$this->authorize('update', $artikel);
// ...
}
// 2. Di rute — sebelum controller dijalankan
Route::put('/artikel/{artikel}', [ArtikelController::class, 'update'])
->middleware('can:update,artikel');
// 3. Atribut, Laravel 13
#[Authorize('update', 'artikel')]
public function update(Artikel $artikel) { /* ... */ }
// 4. Sebagai pemeriksaan biasa
if ($request->user()->can('update', $artikel)) { /* ... */ }
if ($request->user()->cannot('delete', $artikel)) { /* ... */ }
// 5. Seluruh controller resource sekaligus
public function __construct()
{
$this->authorizeResource(Artikel::class, 'artikel');
}
Gate: izin tanpa objek
// AppServiceProvider::boot()
Gate::define('lihat-analitik', fn (User $u) => $u->peran === Peran::Editor);
Gate::define('terbitkan-langsung', fn (User $u) =>
$u->peran === Peran::Editor && $u->terverifikasi);
// Superadmin melewati semua pemeriksaan
Gate::before(fn (User $u) => $u->is_superadmin ? true : null);
Gate::before() mengalahkan segalanya. Kalau ia mengembalikan true, tidak
satu pun policy dijalankan — termasuk aturan yang sengaja kamu buat untuk melindungi data sensitif dari
siapa pun. Kembalikan null (bukan false) saat tidak berlaku, supaya pemeriksaan
berlanjut ke policy seperti biasa.
Pertahanan berlapis
| Lapisan | Melindungi dari | Cukup sendirian? |
|---|---|---|
@can di Blade | Kebingungan pengguna | Tidak — cuma menyembunyikan tombol |
Middleware can: | Akses langsung ke URL | Ya, untuk rute itu |
$this->authorize() | Aksi yang dipanggil dari mana pun | Ya |
| Query yang dibatasi pemilik | Objek milik orang lain | Lapisan terkuat |
// Lapisan terkuat: yang bukan miliknya tidak pernah ditemukan sejak awal
$artikel = $request->user()->artikel()->findOrFail($id); // 404, bukan 403
Membatasi query lebih kuat daripada memeriksa setelahnya. Dengan
Artikel::findOrFail($id) lalu authorize(), ada satu momen ketika objek milik orang
lain sudah termuat di memori — dan satu authorize() yang lupa ditulis berarti data itu terkirim.
Dengan query yang dibatasi relasi pengguna, kelalaian itu tidak mungkin terjadi karena barisnya memang tidak
ada dalam hasil.
Peran dan izin, tanpa paket tambahan
enum Peran: string
{
case Pembaca = 'pembaca';
case Kontributor = 'kontributor';
case Editor = 'editor';
public function bisaMenulis(): bool
{
return $this !== self::Pembaca;
}
public function bisaMenerbitkan(): bool
{
return $this === self::Editor;
}
}
Untuk tiga sampai lima peran yang tetap, enum seperti ini lebih jelas dan lebih cepat daripada tabel izin.
Begitu izin harus bisa diatur pengguna lewat antarmuka, barulah tabel permissions — atau paket
peran-izin — mulai terbayar.
Latihan: buat ArtikelPolicy, pasang authorizeResource di controller, lalu
tulis dua tes: satu memastikan penulis bisa mengubah artikelnya sendiri, satu memastikan pengguna lain
menerima 403. Setelah itu ubah query-nya jadi $user->artikel()->findOrFail() dan amati
statusnya berubah jadi 404.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.