Laravel Policy dan Gate untuk Otorisasi

Pengguna sudah login, tetapi apakah ia boleh menghapus pesanan milik orang lain? Banyak aplikasi Laravel yang berhenti pada pengecekan "sudah login" lalu lupa memeriksa "boleh mela...

Laravel Policy dan Gate untuk Otorisasi

Pengguna sudah login, tetapi apakah ia boleh menghapus pesanan milik orang lain? Banyak aplikasi Laravel yang berhenti pada pengecekan "sudah login" lalu lupa memeriksa "boleh melakukan apa". Akibatnya pengguna biasa bisa mengubah data pengguna lain hanya dengan mengganti angka ID di URL. Laravel menyediakan Gate dan Policy khusus untuk masalah ini, dan memakainya jauh lebih rapi daripada menaruh if di setiap controller.

Autentikasi dan Otorisasi Bukan Hal yang Sama

  • Autentikasi menjawab "siapa Anda?" Contohnya form login dan middleware auth.
  • Otorisasi menjawab "apa yang boleh Anda lakukan?" Contohnya hanya pemilik pesanan yang boleh mengubahnya, atau hanya admin yang boleh menghapus produk.

Kesalahan otorisasi masuk daftar risiko teratas aplikasi web karena sulit terlihat saat pengembangan: semuanya tampak normal sampai ada orang yang mencoba URL milik pengguna lain. Untuk peran dan permission berbasis paket, lihat juga autentikasi dan role permission dengan Spatie.

Gate: Aturan Sederhana Berbentuk Closure

Gate cocok untuk aturan yang tidak terikat pada satu model, misalnya "hanya admin yang boleh membuka halaman laporan". Daftarkan di AppServiceProvider:

use Illuminate\Support\Facades\Gate;
use App\Models\User;

public function boot(): void
{
    Gate::define('lihat-laporan', function (User $user) {
        return $user->role === 'admin';
    });
}

Pemakaiannya:

if (Gate::allows('lihat-laporan')) {
    // tampilkan laporan
}

Gate::authorize('lihat-laporan'); // lempar 403 otomatis bila ditolak

Policy: Aturan Per Model

Begitu aturan menyangkut sebuah model (lihat, ubah, hapus pesanan), Policy lebih tepat karena semua aturan satu model terkumpul di satu kelas. Buat dengan:

php artisan make:policy PesananPolicy --model=Pesanan

Laravel mendeteksi pasangan Pesanan dan PesananPolicy secara otomatis. Isi method sesuai aturan bisnis:

class PesananPolicy
{
    public function viewAny(User $user): bool
    {
        return true;
    }

    public function view(User $user, Pesanan $pesanan): bool
    {
        return $user->id === $pesanan->user_id || $user->role === 'admin';
    }

    public function update(User $user, Pesanan $pesanan): bool
    {
        // Pesanan yang sudah dibayar tidak boleh diubah siapa pun.
        return $user->id === $pesanan->user_id && $pesanan->status === 'baru';
    }

    public function delete(User $user, Pesanan $pesanan): bool
    {
        return $user->role === 'admin';
    }
}

Perhatikan method update: otorisasi bukan hanya soal peran, tetapi juga status data. Aturan seperti ini sangat sering diperlukan dan paling rapi ditulis di policy.

Memakai Policy di Controller

public function update(Request $request, Pesanan $pesanan)
{
    $this->authorize('update', $pesanan);   // 403 jika tidak boleh

    $pesanan->update($request->validated());
    return redirect()->route('pesanan.show', $pesanan);
}

Untuk controller resource, satu baris di konstruktor cukup menjaga semua method standar sekaligus:

public function __construct()
{
    $this->authorizeResource(Pesanan::class, 'pesanan');
}

Kini index, show, update, dan destroy otomatis diperiksa ke method policy yang sesuai.

Di Blade dan Route

@can('update', $pesanan)
    <a href="{{ route('pesanan.edit', $pesanan) }}">Ubah</a>
@endcan

Route::put('/pesanan/{pesanan}', [PesananController::class, 'update'])
    ->middleware('can:update,pesanan');

Menyembunyikan tombol saja tidak cukup. Tombol hanya soal tampilan; yang melindungi data adalah pengecekan di sisi server (controller atau middleware). Selalu pasang keduanya.

Mengizinkan Admin Tanpa Menulis Ulang Setiap Method

Metode before di policy dijalankan lebih dulu. Cocok untuk memberi akses penuh pada super admin:

public function before(User $user, string $ability): ?bool
{
    if ($user->role === 'super_admin') {
        return true;      // lolos tanpa cek lain
    }
    return null;          // lanjut ke method policy biasa
}

Kembalikan null (bukan false) agar pengecekan berlanjut untuk pengguna lain.

Pesan Penolakan yang Jelas

Secara bawaan penolakan menghasilkan halaman 403 generik. Untuk aturan yang sering membingungkan pengguna, kembalikan alasan lewat Response::deny() agar mereka tahu apa yang harus dilakukan:

use Illuminate\Auth\Access\Response;

public function update(User $user, Pesanan $pesanan): Response
{
    if ($pesanan->status !== 'baru') {
        return Response::deny('Pesanan yang sudah dibayar tidak dapat diubah.');
    }

    return $user->id === $pesanan->user_id
        ? Response::allow()
        : Response::denyAsNotFound();   // sembunyikan keberadaan data
}

denyAsNotFound() berguna untuk data sensitif: pengguna yang tidak berhak menerima 404, bukan 403, sehingga ia tidak bisa menebak ID mana yang ada di sistem.

Menyaring Daftar, Bukan Hanya Satu Data

Policy memeriksa satu data pada satu waktu. Untuk halaman daftar, filter langsung di query agar pengguna hanya menerima data miliknya, dan jangan mengandalkan pengecekan satu per satu setelah semua data diambil:

public function index(Request $request)
{
    $query = Pesanan::query()->latest();

    if ($request->user()->role !== 'admin') {
        $query->where('user_id', $request->user()->id);
    }

    return view('pesanan.index', ['pesanan' => $query->paginate(20)]);
}

Pola ini lebih efisien dan menghindari kebocoran lewat jumlah total, hitungan halaman, atau pencarian. Gabungkan dengan optimasi query Eloquent agar daftar tetap cepat.

Aksi Tanpa Model: create dan viewAny

Untuk aksi yang belum punya instance model, seperti membuat data baru, teruskan nama kelasnya:

$this->authorize('create', Pesanan::class);

@can('create', App\Models\Pesanan::class)
    <a href="{{ route('pesanan.create') }}">Buat Pesanan</a>
@endcan

Method create(User $user) di policy tidak menerima parameter model, sehingga cocok untuk aturan seperti "hanya pengguna yang paketnya masih aktif boleh membuat pesanan".

Menguji Otorisasi

public function test_pengguna_tidak_bisa_mengubah_pesanan_orang_lain(): void
{
    $pemilik = User::factory()->create();
    $penyusup = User::factory()->create();
    $pesanan = Pesanan::factory()->for($pemilik)->create();

    $this->actingAs($penyusup)
        ->put("/pesanan/{$pesanan->id}", ['catatan' => 'diubah'])
        ->assertForbidden();
}

Satu test seperti ini menangkap kebocoran data yang tidak akan terlihat saat Anda mencoba manual dengan akun sendiri. Pelajari dasar pengujiannya di testing otomatis di Laravel.

Alur Kerja yang Disarankan

  1. Daftar semua aksi yang bisa dilakukan pada sebuah model: lihat, buat, ubah, hapus, dan aksi khusus seperti menyetujui atau membatalkan.
  2. Tulis aturan tiap aksi dalam satu kalimat bahasa sehari-hari sebelum menulis kode, misalnya "hanya pemilik, selama status masih baru".
  3. Terjemahkan kalimat itu ke method policy, lalu tulis satu test untuk kasus diizinkan dan satu untuk kasus ditolak.
  4. Pasang authorize di controller dan @can di Blade, kemudian periksa daftar halaman agar data difilter sejak query.

Dengan langkah ini, aturan akses terdokumentasi lewat test dan mudah ditinjau ulang oleh anggota tim lain saat aplikasi bertambah besar.

Kesalahan Umum

  • Hanya memakai middleware auth dan mengira sudah aman.
  • Mengambil data dengan Pesanan::find($id) tanpa pengecekan pemilik. Alternatifnya: auth()->user()->pesanan()->findOrFail($id).
  • Menyembunyikan menu di Blade tetapi route-nya tidak dilindungi.
  • Mengembalikan false dari before sehingga mematikan seluruh policy.
  • Tidak menulis test untuk kasus penolakan, hanya kasus berhasil.

Otorisasi yang baik adalah bagian dari checklist keamanan aplikasi Laravel. Mulailah dari model yang menyimpan data pribadi atau uang, lalu perluas ke seluruh aplikasi.

Ingin langsung mempraktikkannya?

Memiliki Membership PRO untuk mengunduh aplikasi Laravel, mengikuti kursus, dan menyimpan tutorial favorit.

Lihat Membership PRO Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Laravel Policy Gate Programming & Laravel
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →