Laravel’de Policy + Gate ile Yetkilendirme: UI’dan API’ye Tutarlı İzin Kontrolü
Policy ve Gate ile rol bazlı değil, aksiyon bazlı yetki kurgusu kurarak UI ve API’de tutarlılık sağlayın.
Neden yetkilendirme “sonradan eklenen” bir detay olmamalı?
Laravel’de kimlik doğrulama (auth) çoğu projede hızla oturur; asıl sorun yetkilendirme (authorization) tarafında çıkar: aynı kuralın hem Blade’de, hem Controller’da, hem API endpoint’inde tekrar edilmesi… Bu tekrar, güvenlik açığı ve bakım maliyeti demektir.
Bu yazıda Policy + Gate yaklaşımıyla aksiyon bazlı (create/update/delete gibi) net bir yetki katmanı kuracağız. Örnek: Bir “Teklif (Offer)” kaydını kimler güncelleyebilir?
Senaryo: Offer güncelleme kuralı
Kurallarımız:
- Admin her şeyi yapabilir.
- Offer’ın sahibi güncelleyebilir.
- Offer “approved” olmuşsa sahibi artık güncelleyemez.
Not: Rol bazlı kontrol ("is_admin") yerine mümkün olduğunca iş kuralı bazlı kontrol yazmak daha sürdürülebilirdir.
1) Policy oluşturma
php artisan make:policy OfferPolicy --model=Offer
app/Policies/OfferPolicy.php:
namespace App\Policies;
use App\Models\Offer;
use App\Models\User;
class OfferPolicy
{
public function update(User $user, Offer $offer): bool
{
if ($user->is_admin) {
return true;
}
if ($offer->status === 'approved') {
return false;
}
return $offer->user_id === $user->id;
}
}
Policy auto-discovery
Laravel 8+ ile çoğu projede Policy’ler auto-discovery ile bulunur. Yine de netlik isterseniz AuthServiceProvider içine map edebilirsiniz.
2) “Her şeye izin veren” admin kuralı: before()
Üstte is_admin kontrolünü her metoda yazmak yerine Policy içinde before kullanabilirsiniz:
public function before(User $user, string $ability): bool|null
{
return $user->is_admin ? true : null;
}
null döndürmek, Laravel’in ilgili metoda (update/delete vs.) devam etmesini sağlar.
3) Controller’da güvenli kullanım (authorize)
public function update(Request $request, Offer $offer)
{
$this->authorize('update', $offer);
$offer->update($request->validate([
'title' => ['required', 'string', 'max:120'],
'price' => ['required', 'numeric', 'min:0'],
]));
return response()->json(['data' => $offer]);
}
Bu yaklaşımın artısı: Policy tek kaynak olur; kural değişirse her yer otomatik güncellenir.
4) Blade tarafında aynı kuralı kullanma (@can)
@can('update', $offer)
<a href="{{ route('offers.edit', $offer) }}">Düzenle</a>
@endcan
UI’da butonu saklamak “güvenlik” değildir; ama kullanıcı deneyimi için önemlidir. Güvenlik asıl olarak Controller/API tarafındaki authorize ile sağlanır.
5) Gate ile “model bağımsız” yetkiler
Bazı yetkiler modele bağlı değildir: örneğin “Rapor indirebilir mi?” gibi.
AuthServiceProvider içinde:
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('download-reports', fn (User $user) => $user->plan === 'pro');
}
Kullanım:
$this->authorize('download-reports');
Blade:
@can('download-reports')
<button>Raporu indir</button>
@endcan
6) İpuçları: Yetkilendirme katmanını “temiz” tutmak
- Policy metotlarını kısa tutun; karmaşık kuralları modele/servise taşıyın.
statusgibi sabit değerler için enum kullanın (PHP 8.1+).authorizeResource()ile resource controller’larda otomatik yetkilendirme bağlayın.
Sonuç
Policy + Gate ile yetkilendirmeyi tek yerde toplayarak:
- UI, API ve arka plan işlerinde tutarlı davranış
- Daha az kopya koşul
- Daha az güvenlik açığı
elde edersiniz. Projeniz büyüdükçe bu disiplin, performanstan çok bakım maliyetini düşürür.