Laravel Access Control

Tidak seperti pada Symfony, CakePHP, Phalcon, Yii atau Zend Framework, dll; pada Laravel dan Lumen tidak ada built-in fitur seperti ACL, MAC, DAC, RBAC atau apapun itu namanya. Beberapa package memang tersedia untuk menangani hal ini seperti Sentry, Entrust, dll.

Pengalaman terakhir saya dengan beberapa packages tersebut lumayan jelek. Seperti misalnya setelah menggunakan Sentry ntah kenapa menjadi terasa lebih lambat. Dan Sentry ini sudah tidak di-update lagi oleh pembuatnya Cartalyst. Melainkan digantikan dengan Sentinel.

Pengalaman lagi dengan Entrust di Laravel 4. Saat itu Entrust v1 masih menggunakan “versi development” dari Ardent sebagai dependency-nya. Hal itu membuat banyak masalah. Apalagi saya menggunakan mininum-stability: "stable" pada composer.json.

Membuat Access Control sendiri

Disini saya akan bahas bagaimana membuat access control sederhana. Saya akan fokus pada kesederhanaan, agar anda bisa mengerti hal basic pada access control. Tentu saja anda bisa modifikasi sendiri sesuai dengan kebutuhkan anda nantinya.

Migrations

Untuk membuat ACL sendiri caranya dengan membuat migrations/table schema

// roles
$table->increments('id');
$table->string('name');
// role_user
$table->unsignedInteger('user_id');
$table->unsignedInteger('role_id');

$table->primary(['user_id', 'role_id']);

Relationship

Setelah itu kita buat relasi dengan menggunakan Eloquent.

<?php namespace App;

// ...
use Illuminate\Database\Eloquent\Model;

class User extends Model 
{
    // ...
    
    public function roles()
    {
        return $this->belongsToMany('Role');
    }
    
    // ...
}
<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model 
{
    // ...
    
    public function users()
    {
        return $this->belongsToMany('User');
    }
    
    // ...
}

Fitur Role Assignment/Revoke

Tambahkan fitur untuk assign atau revoke role.

<?php namespace App;

// ...
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // ...
    
    public function roles()
    {
        return $this->belongsToMany('Role');
    }
    
    public function assignRole($role)
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        return $this->roles()->attach($role);
    }
    
    public function revokeRole($role)
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        return $this->roles()->detach($role);
    }
    
    // ...
}

Fitur Pengecekan Role

<?php namespace App;

// ...
use Illuminate\Database\Eloquent\Model;

class User extends Model 
{
    // ...
    
    public function roles()
    {
        return $this->belongsToMany('Role');
    }
    
    public function assignRole($role)
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        return $this->roles()->attach($role);
    }
    
    public function revokeRole($role)
    {
        if (is_string($role)) {
            $role = Role::where('name', $role)->first();
        }

        return $this->roles()->detach($role);
    }
    
    public function hasRole($name)
    {
        foreach($this->roles as $role)
        {
            if ($role->name === $name) return true;
        }
        
        return false;
    }
    
    // ...
}

Dan sudah selesai 😐

Percaya atau tidak, membuat ini sebenernya cuma butuh waktu sekitar 2-6 menit 😐

Cara menggunakan

$user = User::find(1);

// Jadikan user ini sebagai admin
$user->assignRole('admin');

// Keluarkan user ini dari admin
$user->revokeRole('admin');

Contoh pada controller untuk mengecek apakah current user memiliki role admin

if (Auth::user()->hasRole('admin'))
{
    return Redirect::to('/backend');
}

Contoh pengecekan menu di view

@if (Auth::user()->hasRole('moderator'))
    <a href="#">Go to Backend</a>
@endif

Middleware dan Filter

Filter

Jika anda menggunakan Laravel 4, anda bisa menggunakan filter. Letakkan ini pada app/filters.php

Route::filter('role', function($name)
{
    if (Auth::check() && ! Auth::user()->hasRole($name))
    { 
        return App::abort(401, 'Unauthorized');
    }
});

Cara menggunakan filter di route

Route::get('/backend', [
    'uses'   => 'BackendController@index',
    'before' => 'role:admin'
]);

Middleware (Laravel 5.0)

Berhubung fitur middleware parameter belum ada di Laravel 5.0, anda bisa menggunakan filter. Karena filter sebenarnya tidak dihilangkan dari core Laravel 5.0. Anda bisa menggunakannya seperti di Laravel 4 seperti biasa.

Middleware (Laravel 5.1)

Buat middleware baru

./artisan make:middleware RoleMiddleware

Edit App\Http\Middleware\RoleMiddleware.php

public function handle($request, Closure $next, $roleName)
{
    if (auth()->check() && ! auth()->user()->hasRole($roleName))
    {
        return abort(401, 'Unauthorized');
    }
    
    return $next($request);
}

Di App\Http\Kernel.php daftarkan middleware ini

protected $routeMiddleware = [
    // ...
    'role' => 'App\Http\Middleware\RoleMiddleware',
];

Cara menggunakan di route

Route::get('/backend', [
    'uses'       => 'BackendController@index',
    'middleware' => 'role:admin',
]);

Kesimpulan

Membuat fitur access control itu sangat mudah. Anda bisa menambahkan fitur-fitur lain seperti “permissions”, multiple role check:

Auth::user()->hasRole(['admin', 'operator', 'moderator']);

// atau

Auth::user()->hasRole('admin', 'operator', 'moderator');
dengan 

Atau fitur-fitur lainnya yang anda butuhkan. Sehingga anda tidak mentok karena bergantung terhadap suatu package.

Jika ada pertanyaan silahkan tanyakan pada kolom komentar.

UPDATE 24 Juni 2015

Contoh code bisa dilihat di https://github.com/mul14/laravel-acl-demo

UPDATE 25 September 2015

Tambahin pengecekan string pada assignRole() dan revokeRole(), juga update source code di GitHub https://github.com/mul14/laravel-acl-demo

20 Komentar

  1. Rizqy berkata:

    hmm sampai sekarang masih penasaran magic dibalik method attach(), kok bisa tau role ini idnya berapa 😮

    1. mul14 berkata:

      Cuma pengecekan code sederhana aja, gak ada yang spesial.

      // Illuminate/Database/Eloquent/Relations/BelongsToMany.php
      
      if ($id instanceof Model) $id = $id->getKey();
      

      Kalau model, maka ambil ID-nya.

      1. Rizqy berkata:

        iya kalau attachnya pakai model atau . misal yg diatas kan pake string ‘admin’, nah gimana bisa tau id dari row yang kolom ‘name’-nya adalah ‘admin’ ini. apa dicari semua kolom gitu? ini yg masih saya belum paham om. 😐

      2. mul14 berkata:

        Kan ada table ketiga yang menghubungkan antara `users` dan `roles`, yaitu `role_user`. Coba cek migration-nya.

    2. mul14 berkata:

      Ah yaa, akhirnya setelah ditanyakan ulang di Slack, saya ngerti maksudnya. Ternyata saya yang salah. Segera di-update artikel dan source code di GitHub.

  2. maaf saya masih awam dengan laravel, mau tanya, untuk menempatkan

    $user = User::find(1);

    // Jadikan user ini sebagai admin
    $user->assignRole(‘admin’);

    // Keluarkan user ini dari admin
    $user->revokeRole(‘admin’);

    di bagian mana ya ?

    1. mul14 berkata:

      Oh, ini cuma contoh untuk menggunakan. Nantinya terserah diletakkan dimana, tergantung dengan kebutuhan. Biasanya sih pada suatu proses dimana user tersebut mau dijadikan admin.

      Tapi untuk belajar, atau cuma untuk nge-test, bisa diletakkan di app/Http/routes.php. Misalnya berisi:

      
      Route::get('/', function () {
      
          $user = User::find(1); // User dengan ID 1
      
          $user->assignRole('admin');
      
      });
      
      

      Lalu cek di database, table role_user, jika ada bertambah isinya, berarti berhasil. Begitu juga dengan revokeRole(), kalau datanya hilang, berarti sudah berhasil.

  3. Agus Edy Cahyono berkata:

    mas mau tanya, ketika saya $user->assignRole(‘admin’) lalu saya check hasilnya selelu false kenapa ya? saya coba pake https://github.com/romanbican/roles juga false.
    mohon pencerahanya 😀

    1. mul14 berkata:

      Wah 😐
      Saya juga bingung kalo gak liat code-nya langsung. Mungkin ada yang kelewat. Coba aja pelajari ini https://github.com/mul14/laravel-acl-demo

  4. Agus Edy Cahyono berkata:

    ok mas udah bisa, mau tanya lagi agak menyimpang dari pembahasan 😀
    akhirnya saya pake https://github.com/romanbican/roles , lalu cara pake di routenya bagaimana? saya pake Route::resource() 😀

    1. mul14 berkata:

      Wah, saya nggak pernah pake itu 😐
      Bikin sendiri lebih gampang. Apalagi sejak di Laravel 5.1.11 selain authentication sudah ada authorization.

      http://laravel.com/docs/5.1/authorization

  5. DeeWie Damayanti berkata:

    Mas, bagaimana cara menampilkan role name yang telah dipakai oleh user, untuk ditampilkan pada view. ? Terima kasih

    1. mul14 berkata:

      Oh, gampang banget itu. Coba deh pake dd() di Auth::user(). Kan keliatan kalau itu adalah object dari App\User. Berarti bisa pake semua yang ada di dalam class-nya. Kan di dalamnya sudah ada

      public function roles()
      {
          return $this->belongsToMany('Role');
      }
      

      Berarti, cara manggilnya cukup dengan

      @foreach(Auth::user()->roles as $role)
        {{ $role->name }}
      @endforeach
      
      1. DeeWie Damayanti berkata:

        Alhamdulillah,.. akhirnya bisa mas,… ^_^ terima kasih Mas.. saya udah mengerti sekarang. Ternyata saya yg salah persepsi ttg konsepnya mas. Jadi sebenernya saya ini kan lagi mengimplementasikan multiple auth dan authorization di Laravel 5.2, awalnya saya memanggil sesuai dgn konsepnya multiple auth, yaitu {{ auth('user')->user()->role()->name }} tapi kenapa selalu undefined property: padahal saya tau udah ada function role() di User model yg berelasi dgn Role model.. akhirnya saya berpikir cara lain dgn memanfaatkan pivot table role_user, tapi tetep sama aja… ^_^.

  6. Gandhy Onlyy berkata:

    utk melakukan validasi di controller
    saya menggunakan

    function __construct() {
        $this->middleware('role:admin');
    }
    

    dapat berjalan dengan baik..
    kalau ingin memvalidasi 2 role bukannya cukup seperti dibawah?

    $this->middleware('role:admin,member');
    

    ato code diatas ada yang perlu diubah?

Tinggalkan Balasan ke Rizqy Batalkan balasan