Laravel Role-based Authentication

Makale İçerik Listesi

Basit Authorization ile Sınırlama

  1. User tablosunda "is_admin" diye bir boolean alanımız olsa ve ona göre belli bir klasör ve sayfaya girebilmesini belirlemek istesek... users tablosunda "is_admin" alanını yaptıktan sonra.
  2. Kullanıcının bu alanını her requesti cevaplandırmadan önce değerlendirmek için bir middleware oluşturuyorum. 
    php artisan make:middleware IsAdminMiddleware
  3. IsAdminMiddleware.php içindeki handle() fonksiyonunda kullanıcı authorized bir user değilse veya authorized olsa da admin değilse 403 ile hata dönmesini istiyorum. 
    public function handle(Request $request, Closure $next)
    {
       if(!auth()->check() || !auth()->user()->is_admin) {
          abort(403);
       }
       return $next($request);
    }
  4. Daha sonra tabii bu yaptığımız Middleware'i app > Http > Kernel.php içinde kaydetmemiz gerekiyor. 
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        ...
        'is_admin' => \App\Http\Middleware\IsAdminMiddleware::class,
    ];
  5. Domain içinde bir subfolder'a sadece authorized olanlar girebilsin, ama bunun da içinde bir alan sadece normal kullanıcılar, bir alan admin kullanıcılar için olsun istersek bunu web.php içinde gruplayarak belirtebiliriz. 
    <?
    Route::group(['middleware' => 'auth'], function() {
       
       Route::group([
            'prefix' => 'admin',
            'middleware' => 'is_admin',
            'as' => 'admin.',
       ], function() {
            Route::get('tasks', [\App\Http\Controllers\Admin\TaskController::class, 'index'])->name('tasks.index');
       });
    
       Route::group([
            'prefix' => 'user',
            'as' => 'user.',
       ], function() {
            Route::get('tasks', [\App\Http\Controllers\User\TaskController::class, 'index'])->name('tasks.index');
       });
    
    });
  6. Hem kullanıcıların hem de adminlerin gördüğü ana bir sayfada kişinin pozisyonuna göre belli navigasyon seçenekleri gösterilsin istersek blade içerisinde @if ile bunu sağlayabiliriz. 
    @if(auth()->user()->is_admin)
        <x-nav-link :href="route('admin.tasks.index')" :active="request()->routeIs('admin.tasks.index')">
            {{ __('All Tasks') }}
        </x-nav-link>
    @else
        <x-nav-link :href="route('user.tasks.index')" :active="request()->routeIs('user.tasks.index')">
            {{ __('My Tasks') }}
        </x-nav-link>
    @endif
  7. Controller tarafında da farklı controllerlar olduğu için admin'e göre ve user'a göre farklı fonksiyonlar yapılır.
    \Admin\TaskController.php
    <?php
    
    namespace App\Http\Controllers\Admin;
    
    use ...
    
    class TaskController extends Controllers
    {
        public function index()
        {
            $tasks = Task::with('user')->orderBy('due_date')->get();
            return view('admin.tasks.index', compact('tasks'));
        }
    
    }

    \User\TaskController.php 

    <?php
    
    namespace App\Http\Controllers\User;
    
    use ...
    
    class TaskController extends Controllers
    {
        public function index()
        {
            $tasks = auth()->user()->tasks;
            return view('user.tasks.index', compact('tasks'));
        }
    
    }

     

Authorization Gate'leri Kullanmak

  1. Ayrıca authorization Gate'leri kullanarak ta yapılabilir. Laravel Docs: Authorization > Gates
  2. Bu şekilde izinler belirttiğimizde blade içerisinde @can('izin_adi') direktifi ile belli html parçalarını ekleyip çıkartabiliriz. 
    @can('tasks_create')
       <a href="{{ route('tasks.create') }}">Add new Task</a>
    @endcan
  3. Bu nevi kapıların kimlere açık olduğunu AuthServiceProvider.php içerisinde belirtiyoruz. 
    public function boot()
    {
       $this->registerPolicies();
    
       Gate::define('tasks_create', function(User $user) { return $user->is_admin; }
       Gate::define('tasks_edit', function(User $user) { return $user->is_admin; }
       Gate::define('tasks_delete', function(User $user) { return $user->is_admin; }
    }
    // fonksiyon true dönen kişi bunu yapabilir oluyor.
  4. AuthServiceProvider'da bunu belirttikten sonra Controller'ın bunu kaale alabilmesi için Controller içinde de $this->authorize('gate_name') dememiz lazım. 
    // TaskController
    
    public function create()
    {
       $this->authorize('tasks_create');
       return view('tasks.create');
    }
  5. Bu durumda eğer kullanıcının o işi yapma yetkisi yoksa, create sayfası yerine 403 hata ekranı döner.
  6. Ama tabii bir kullanıcı tipi için çok sayıda Gate'i AuthServiceProvider içindeki boot() fonksiyonuna yazmak bir noktadan sonra çok kullanışlı ve sürdürülebilir bir çözüm değildir. Çok sayıda Gate'i bir arada toplayarak bir Policy oluşturmak daha doğru çözüm.

Policy'leri Kullanarak Gateleri Gruplamak

  1. Bir policy dosyası oluşturmak için yapmam gereken tek şey terminalden php artisan make:policy TaskPolicy demek. Böyle yapınca app > Policy içersine TaskPolicy dosyası oluşur.
  2. Ama bir Policy'yi belli bir modelin CRUD işlemlerine yönelik olarak ta yapabiliriz. ör. Task'lerin oluşması, güncellemesi ve silinmesi için bir policy. Bu durumda --model ile modelin de ne olacağını bilirsek, Policy o da içine yazılmış şekilde oluşur. 
    php artisan make:policy TaskPolicy --model=task
  3. Policy oluşturduktan sonra eğer Policy ismi ModelPolicy şeklinde kurgulanmışsa, Laravel oluşan Policy'leri otomatik bulur. Bir yere register etmemize gerek olmaz :)
  4. Önceden Gate ile yaptığımız şeyleri daha kullanışlı biçimde Policy içersine ekleyebiliriz.

    class TaskPolicy
    {
        public function create(User $user)
        {
            return $user->is_admin;
        }
    
        public function update(User $user, Task $task)
        {
            return $user->is_admin || (auth()->check() && $task->user_id == auth()->id());
        }
    
        public function delete(User $user, Task $task)
        {
            return $user->is_admin || (auth()->check() && $task->user_id == auth()->id());
        }
    }
  5. Yukardaki örnekte sadece kullanıcının authorized ve admin olmasına bakılmıyor. Aynı zamanda update ve delete işlemleri için o görevi oluşturan kişi olması da kontrol ediliyor.
  6. Yukardaki gibi güvenlik duvarı Policy'ye bağlanınca Controller'da da değişiklik yapmamız lazım. 
    public function create()
    {
       $this->authorize('create', Task::class);
       return view('tasks.create');
    }
    
    public function store(Request $request)
    {
       $this->authorize('create', Task::class);
    
       Task::create($request->only('description','due_date'));
    
       return redirect()->route('tasks.index');
    }
    
    public function edit(Task $task)
    {
       $this->authorize('create', $task);
    
       $task->update($request->only('description','due_date'));
    
       return redirect()->route('tasks.index');
    }
  7. Bu şekilde yaptığımızda Blade dosyalarında da tabii ki update ve delete işlemlerinde task'leri de vermemiz gerekiyor. 
    @can('update', $task)
       <a href="{{ route('tasks.update', $task) }}">Update Task</a>
    @endcan
  8. dsadas

Referanslar

  1. Laravel Docs - Gates - link
  2. Laravel Docs - Policy - link
  3. Video tutorial - link
  4. Online Course - link 
  5. Laracasts Series: Multitenancy in Practice - link
                                                                                                                                      1. Terminalde php artisan make:policy TaskPolicy diyerek
  6.