İçeriğe geç
Muhammet Şafak
Web Geliştirme 3 dk okuma

Laravel policy ve gate ile yetkilendirme

Laravel'de Gate ve Policy ile yetkilendirme mantığını tek yerde toplamak; 'kim neyi yapabilir' kararlarını temiz kurmak.


Yetkilendirme — yani “bu kullanıcı bu işlemi yapabilir mi?” sorusu — her uygulamanın bir yerinde yanıtlanmak zorunda. Soru basit ama yanıtın nerede verildiği önemli. Controller içinde if ($user->id !== $post->user_id) gibi kontroller yazmak başlangıçta masum görünüyor; ama aynı kontrol birkaç farklı yere dağılınca bir kuralı değiştirmek onlarca yeri dolaşmak anlamına geliyor.

Laravel’in Gate ve Policy yapısı bu kararları merkezi ve test edilebilir hale getiriyor.

Gate nedir?

Gate, basit, model tabanlı olmayan yetkilendirme kontrolleri için kullanılıyor. AuthServiceProvider içinde tanımlanıyor:

use Illuminate\Support\Facades\Gate;

Gate::define('update-settings', function ($user) {
    return $user->is_admin;
});

Kullanımı:

if (Gate::allows('update-settings')) {
    // Admin işlemi
}

// Veya kısayol:
Gate::authorize('update-settings'); // İzin yoksa 403 fırlatır

Blade şablonlarında da kullanılabiliyor:

@can('update-settings')
    <a href="/settings">Ayarlar</a>
@endcan

Policy nedir?

Policy, bir Eloquent modeline özgü yetkilendirme mantığını toplayan sınıf. Birden fazla eylemi olan modeller için Gate’den daha düzenli bir yapı sunuyor.

Oluşturmak için:

php artisan make:policy PostPolicy --model=Post

Oluşturulan PostPolicy sınıfında metotlar yetkilendirme kurallarını tanımlıyor:

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id || $user->is_admin;
    }
}

AuthServiceProvider içinde kaydetmek gerekiyor:

protected $policies = [
    Post::class => PostPolicy::class,
];

Policy’yi kullanmak

Controller içinde:

public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);

    // Yetki varsa buraya geliyor
    $post->update($request->validated());
    return redirect()->route('posts.show', $post);
}

authorize('update', $post) çağrısı, mevcut kullanıcı ve $post nesnesiyle PostPolicy@update metodunu çalıştırıyor. false dönerse otomatik olarak 403 yanıtı üretiyor.

Blade tarafında:

@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">Düzenle</a>
@endcan

@can('delete', $post)
    <form action="{{ route('posts.destroy', $post) }}" method="POST">
        @csrf @method('DELETE')
        <button>Sil</button>
    </form>
@endcan

Gate mı, Policy mi?

İkisi arasında seçim yaparken basit bir kural uyguladım:

  • Belirli bir modele bağlı değilse ve tek bir kontrol söz konusuysa Gate yeterli.
  • Bir modelin birden fazla eylemi (görüntüle, güncelle, sil, yayınla…) için kontrol gerekiyorsa Policy daha uygun.

Kullanıcı yönetimi, içerik yönetimi gibi alanlarda Policy kurmak, o alanın tüm yetkilendirme mantığını tek dosyada topluyor. Bir kural değiştiğinde tek yer açılıyor.

Policy’yi test etmek

Bu yapının fark yarattığı bir diğer alan: test edilebilirlik. Policy bir sınıf olduğu için doğrudan örneklenip test edilebiliyor; controller’ı ayağa kaldırmak gerekmiyor:

public function test_kullanici_kendi_yazilarini_guncelleyebilir()
{
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();
    $policy = new PostPolicy();

    $this->assertTrue($policy->update($user, $post));
}

public function test_kullanici_baskasinin_yazisini_guncelleyemez()
{
    $yazar  = User::factory()->create();
    $misafir = User::factory()->create();
    $post    = Post::factory()->for($yazar)->create();
    $policy  = new PostPolicy();

    $this->assertFalse($policy->update($misafir, $post));
}

Bu testleri controller içine gömülü bir if bloğuyla yazmak mümkün değil; o bloğu HTTP isteği üzerinden dolaylı test etmek zorunda kalırdım. Policy sayesinde kural doğrudan, hızlıca ve bağımsız test ediliyor.

before metodu: yönetici istisnası

Policy sınıflarında before adlı özel bir metot tanımlayabiliyorsunuz. Bu metot, diğer tüm policy metotlarından önce çalışıyor ve null dışında bir değer döndürürse diğer metotlar hiç çalışmıyor:

class PostPolicy
{
    public function before(User $user, string $ability): ?bool
    {
        if ($user->is_superadmin) {
            return true; // Süper admin her şeyi yapabilir
        }

        return null; // Normal akışa devam et
    }

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

Burada null döndürmek önemli: false döndürseydiniz süper admin de dahil herkesin erişimini engellerdiniz. null dönünce “karar vermedim, diğer metoda bak” anlamına geliyor. Bu farkı ilk kez görenler false döndürme hatasına düşebiliyor.

Politika olmadan sonrası

Bu yapıyı kullanmadan önce yetkilendirme kontrollerini bazı yerlerde controller’a, bazı yerlerde middleware’e yazmıştım. Modelin sahibinin kim olduğuna dair kural üç farklı dosyada biraz farklı şekillerde duruyordu. Policy ile bunların hepsini tek sınıfa topladım; kural değiştiğinde tek dosyaya bakıyorum. Uygulamaya yeni bir rol eklendiğinde veya mevcut bir kural yumuşatıldığında, değişikliği tek bir noktaya yapmak ve testleri çalıştırmak yeterli oluyor. Bu güven, dağıtık if bloklarıyla mümkün değildi.

Authentication ile yetkilendirmeyi karıştırmak da başlangıçta yaptığım bir hataydı. Authentication “bu kişi kim?” sorusunu yanıtlar; yetkilendirme ise “bu kişi bunu yapabilir mi?” sorusunu. Laravel’de bu iki katman birbirinden ayrı: Auth ve middleware authentication’ı, Gate ve Policy ise yetkilendirmeyi hallediyor. Bu ayrımı netleştirince nereye ne yazacağım da netleşti. Bir kullanıcı giriş yapmış olabilir ama bu onun her kaynağa erişebileceği anlamına gelmiyor; kimliği doğrulanmış ama yetkisiz — iki farklı kontrol, iki farklı katman.

Etiketler: #Laravel
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi