BAB 14: Authorization

Mengontrol akses dengan Gates dan Policies untuk membatasi tindakan pengguna di Laravel.

Setelah sistem login berjalan, aplikasi catatan kita tahu siapa yang sedang menggunakannya. Tapi pertanyaan “siapa yang boleh melakukan apa” belum terjawab. Saat ini, semua pengguna yang login bisa mengakses, mengubah, bahkan menghapus catatan siapapun — cukup dengan menebak URL-nya.

Authorization adalah lapisan yang menjawab pertanyaan itu. Bukan tentang apakah pengguna sudah login, melainkan tentang apakah pengguna yang login ini berhak melakukan tindakan tertentu.

Gates vs Policies

Laravel menyediakan dua mekanisme authorization: Gates dan Policies. Keduanya melakukan hal yang sama, tapi di konteks yang berbeda.

GatesPolicies
BentukClosure sederhanaClass terstruktur
Cocok untukPengecekan sederhana, non-modelOperasi CRUD pada model
Lokasi definisiAppServiceProviderFile terpisah di app/Policies/
Contoh kasus”Apakah user adalah admin?""Apakah user boleh mengedit catatan ini?”

Untuk model Catatan yang kita punya, Policies adalah pilihan yang tepat karena semua tindakan berputar di sekitar model tersebut.

Membuat Policy

Gunakan Artisan untuk membuat policy:

php artisan make:policy CatatanPolicy --model=Catatan

Flag --model=Catatan membuat Laravel sekaligus membuat stub untuk method-method CRUD standar: viewAny, view, create, update, delete, restore, forceDelete.

File policy akan muncul di app/Policies/CatatanPolicy.php. Isi dengan logika kepemilikan:

<?php
// app/Policies/CatatanPolicy.php

namespace App\Policies;

use App\Models\Catatan;
use App\Models\User;

class CatatanPolicy
{
    public function viewAny(User $user): bool
    {
        return true; // semua pengguna yang login boleh melihat daftarnya
    }

    public function view(User $user, Catatan $catatan): bool
    {
        return $user->id === $catatan->user_id;
    }

    public function create(User $user): bool
    {
        return true; // semua pengguna yang login boleh membuat catatan baru
    }

    public function update(User $user, Catatan $catatan): bool
    {
        return $user->id === $catatan->user_id;
    }

    public function delete(User $user, Catatan $catatan): bool
    {
        return $user->id === $catatan->user_id;
    }
}

Polanya konsisten: untuk tindakan yang melibatkan instance model (view, update, delete), kita bandingkan $user->id dengan $catatan->user_id. Untuk tindakan yang tidak melibatkan instance model (viewAny, create), method hanya menerima $user.

Laravel secara otomatis menemukan policy yang cocok untuk model berdasarkan konvensi penamaan — Catatan model dipasangkan ke CatatanPolicy. Tidak perlu registrasi manual selama keduanya ada di folder yang benar (app/Models/ dan app/Policies/).

Menggunakan Policy di Controller

Ada dua cara menggunakan policy di controller. Cara pertama dengan $this->authorize(), yang tersedia di semua controller yang extend Controller:

<?php
// app/Http/Controllers/CatatanController.php

namespace App\Http\Controllers;

use App\Models\Catatan;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class CatatanController extends Controller
{
    public function index(): View
    {
        $catatan = auth()->user()->catatan()->latest()->paginate(10);

        return view('catatan.index', compact('catatan'));
    }

    public function show(Catatan $catatan): View
    {
        $this->authorize('view', $catatan);

        return view('catatan.show', compact('catatan'));
    }

    public function edit(Catatan $catatan): View
    {
        $this->authorize('update', $catatan);

        return view('catatan.edit', compact('catatan'));
    }

    public function update(Request $request, Catatan $catatan): RedirectResponse
    {
        $this->authorize('update', $catatan);

        $data = $request->validate([
            'judul'     => 'required|string|min:3|max:200',
            'isi'       => 'required|string|min:10',
            'prioritas' => 'required|in:rendah,sedang,tinggi',
        ]);

        $catatan->update($data);

        return redirect()->route('catatan.show', $catatan)
            ->with('sukses', 'Catatan berhasil diperbarui.');
    }

    public function destroy(Catatan $catatan): RedirectResponse
    {
        $this->authorize('delete', $catatan);

        $catatan->delete();

        return redirect()->route('catatan.index')
            ->with('sukses', 'Catatan berhasil dihapus.');
    }
}

Ketika $this->authorize() menemukan bahwa pengguna tidak berhak, Laravel otomatis mengembalikan response HTTP 403 Forbidden. Kita tidak perlu menulis if dan abort(403) secara manual.

Cara kedua adalah menggunakan Gate::authorize() dari Illuminate\Support\Facades\Gate — ini berguna di luar controller, misalnya di service class.

Policy di Blade Template

Authorization juga bisa diperiksa langsung di template Blade menggunakan direktif @can dan @cannot:

{{-- resources/views/catatan/show.blade.php --}}
<x-app-layout>
    <div class="max-w-2xl mx-auto mt-8">
        <h1 class="text-2xl font-bold">{{ $catatan->judul }}</h1>
        <p class="mt-4 text-gray-700">{{ $catatan->isi }}</p>

        @can('update', $catatan)
            <div class="mt-6 flex gap-3">
                <a href="{{ route('catatan.edit', $catatan) }}"
                   class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">
                    Edit
                </a>
            </div>
        @endcan

        @can('delete', $catatan)
            <form method="POST" action="{{ route('catatan.destroy', $catatan) }}"
                  class="inline">
                @csrf
                @method('DELETE')
                <button type="submit"
                        class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
                        onclick="return confirm('Hapus catatan ini?')">
                    Hapus
                </button>
            </form>
        @endcan
    </div>
</x-app-layout>

Tombol edit dan hapus hanya muncul jika pengguna yang sedang login adalah pemilik catatan tersebut. Ini adalah authorization di layer tampilan — mencegah pengguna melihat aksi yang memang tidak bisa mereka lakukan.

Menyembunyikan tombol di Blade bukan pengganti $this->authorize() di controller. Pengguna masih bisa mengirim request langsung tanpa melalui UI. Selalu periksa authorization di controller.

Gates untuk Kasus Sederhana

Untuk pengecekan yang tidak terikat pada model tertentu — misalnya memastikan pengguna adalah admin — Gates lebih ringkas. Daftarkan di AppServiceProvider:

<?php
// app/Providers/AppServiceProvider.php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Gate::define('akses-admin', function (User $user) {
            return $user->role === 'admin';
        });
    }
}

Gunakan di controller atau Blade:

// Di controller
Gate::authorize('akses-admin');

// Atau dengan if manual
if (Gate::denies('akses-admin')) {
    abort(403, 'Halaman ini hanya untuk admin.');
}
@can('akses-admin')
    <a href="/admin">Panel Admin</a>
@endcan

Gate before memungkinkan kita membuat “super user” yang bisa melewati semua pengecekan:

// app/Providers/AppServiceProvider.php
Gate::before(function (User $user, string $ability) {
    if ($user->role === 'superadmin') {
        return true;
    }
});

Ketika Gate::before mengembalikan true, semua pengecekan gate dan policy lainnya dilewati untuk pengguna tersebut.

Latihan

Coba kerjakan pengembangan berikut:

  1. Policy dengan kondisi tambahan — Tambahkan method forceDelete ke CatatanPolicy yang hanya mengizinkan pengguna dengan role === 'admin' untuk menghapus catatan permanen (bukan soft delete). Gunakan di route baru /catatan/{id}/force-delete.

  2. Pesan error kustom — Ubah method update dan delete di CatatanPolicy agar mengembalikan Response::deny('...') dengan pesan yang spesifik, bukan sekadar false. Tampilkan pesan tersebut di view menggunakan Gate::inspect().

  3. Gate untuk pembatasan jumlah — Buat gate buat-catatan yang menolak pembuatan catatan baru jika pengguna sudah memiliki lebih dari 10 catatan aktif. Integrasikan dengan CatatanController@create.

Penutup Bab

Gates dan Policies melengkapi sistem autentikasi yang kita bangun di bab sebelumnya. Authentication menjawab “siapa kamu”, authorization menjawab “apa yang boleh kamu lakukan”. Keduanya bekerja bersama untuk memastikan setiap tindakan di aplikasi hanya bisa dilakukan oleh pihak yang berhak.

Dengan dua lapisan ini terpasang, fondasi aplikasi kita sudah cukup kokoh. Langkah selanjutnya adalah berkenalan dengan Artisan — CLI bawaan Laravel yang sudah kita gunakan beberapa kali sejak awal, tapi belum pernah kita telusuri kemampuannya secara penuh.

Referensi

  1. 1Authorization — Laravel 12.x Documentation
  2. 2Creating Policies — Laravel 12.x Authorization Documentation