← Semua pembelajaran / Laravel Nol → Enterprise
Fase 4 · Autentikasi & Keamanan

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.

Sumber asli laravel.com Resmi Rangkuman ~7 menit baca

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.
  • @can di 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

LapisanMelindungi dariCukup sendirian?
@can di BladeKebingungan penggunaTidak — cuma menyembunyikan tombol
Middleware can:Akses langsung ke URLYa, untuk rute itu
$this->authorize()Aksi yang dipanggil dari mana punYa
Query yang dibatasi pemilikObjek milik orang lainLapisan 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.