laravel-permissionを利用したロールベースアクセス制御（RBAC）を実装する

## はじめに
RBAC方式でアクセス制御をすることになったので、larave-permissionの概要を簡単にまとめていきます。
初心者の方向けに簡単に概要を把握できるように書いていきます。

## laravel-permissionとは
Laravelのプラグインの1つで、**RBAC方式**などの**アクセス制御**を実現することができます。

## RBAC方式とは
RBAC方式とは、「**ある対象（例えばユーザー）**」に「**ロール（一般ユーザー・管理者など）**」という概念が割当てられるようにしたものです。この**ロール**に対して、「**何ができるか**」という権限が付与される方式のことです。

例えば、「勉強会の一般参加者は**GUESTカード**を渡され、運営側の人は**STAFFカード**が渡される。**STAFFカード**を持っている人は**特定の部屋に入ることができる**。」というように特定の**ロール**に、特定の**行動許可**が付与されるようなイメージです。

## インストール＆設定方法
larave-permissionはcomposer経由でインストールすることが可能です。
```
composer require spatie/laravel-permission
```
`config/app.php`に下記1行を追加します。
```php:config/app.php
'providers' => [
    // ...
    // 追記
    Spatie\Permission\PermissionServiceProvider::class,
];
```
続いて、下記コマンドを実行します。artisan vendor:publishを実行すると、下記の設定ファイル（config/permission.php）と、マイグレーションファイルが作成されます。
- /config/permission.php
- /database/migrations/yyyy_mm_dd_hhmmss_create_permission_tables.php
```php
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
```
キャッシュをクリアして、
```php
php artisan optimize:clear
```
マイグレーションを実行します。
```php
php artisan migrate
```
すると、以下のように5つの新規テーブルが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270253/9613748d-3dc8-fa6b-49f2-b27b139ca5a1.png)
最後に、`User`モデルに`Spatie\Permission\Traits\HasRoles`を追加すれば、初期の設定は完了です。
```diff_php:App\Models\User.php
use Illuminate\Foundation\Auth\User as Authenticatable;
+ use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
+   use HasRoles;

    // ...
}
```

## ロールと権限の作成
まずは準備として、ユーザー、ロール、権限を作成し、それぞれを紐づけていきます。流れとしては以下の通り
- `山田太郎（管理者）`、`山田花子（編集者）`というテストユーザーを作成する
- `admin`、`editor`というロールを作成する
- `edit articles`、`delete articles`という権限を作成する
- `admin`ロールに対して、`edit articles`、`delete articles`権限を紐づける
- `editor`ロールに対して、`edit articles`権限を紐づける
- `山田太郎（管理者）`ユーザーに`admin`ロールを割り当て、`山田花子（編集者）`ユーザーに`editor`ロールを割り当てる

シーダーファイルとしては以下のようになります。
```php:Database\Seeders\RolesAndPermissionsSeeder.php
<?php

declare(strict_types=1);

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class RolesAndPermissionsSeeder extends Seeder
{
    /**
     * Run the database seeds.
     * @return void
     */
    public function run(): void
    {
        // adminユーザーを作成
        $adminUser = new User();
        $adminUser->name = '山田太郎（管理者）';
        $adminUser->email = 'aaa@bbb.com';
        $adminUser->password = Hash::make('password');
        $adminUser->save();

        // editorユーザーを作成
        $editorUser = new User();
        $editorUser->name = '山田花子（編集者）';
        $editorUser->email = 'aaa@bbb.com';
        $editorUser->password = Hash::make('password');
        $editorUser->save();

        // Roleの作成
        $admin = Role::create(['name' => 'admin']);
        $editor = Role::create(['name' => 'editor']);

        // Permissionの作成
        $permissionOfEditArticles = Permission::create(['name' => 'edit articles']);
        $permissionOfDeleteArticles = Permission::create(['name' => 'delete articles']);

        // RoleとPermissionを関連付け
        $admin->givePermissionTo($permissionOfEditArticles);
        $admin->givePermissionTo($permissionOfDeleteArticles);
        $editor->givePermissionTo($permissionOfEditArticles);

        // UserにRoleを割り当て
        $adminUser->assignRole('admin');
        $editorUser->assignRole('editor');
    }
}
```
作成したシーダーファイルを実行して、準備は完了です。
```php
php artisan db:seed --class=RolesAndPermissionsSeeder
```
## アクセス制御を実装する3つのパターン
ここからは実際にアクセス制御をする際にどうのように実装すればいいのか？というところで、3つのパターンを例として説明していきます
結論、②のコントローラー内で実装することが個人的には多いですが、念のためRouter内で実装する①と、bladeで実装する③も記載します
### ①Router内で認可処理を実施する
まずは、Router内で処理を実施するために、`Kernel.php`にミドルウェアとして登録します
```php:App/Http/Kernel.php
protected $routeMiddleware = [
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
    'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
    'roleOrPermission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
];
```
これにより、ミドルウェアとして使用できるようになるので、ユースケースに合わせて下記のように実装していきます
```php:api.php
// Roleによるチェック
Route::group(['middleware' => ['role:admin|editor']], function () {
    // ・・・
});

// Permissionによるチェック
Route::group(['middleware' => ['permission:delete articles']], function () {
    // ・・・
});

// Role or Permissionによるチェック
Route::group(['middleware' => ['roleOrPermission:editor|read']], function () {
    // ・・・
});
```

### ②コントローラー内で認可処理を実施する
コントローラーで処理をするパターンです。個人的な感覚ではこちらのパターンで実装することが多いです
```php:App\Http\Controllers\TestController
class TestController extends Controller
{
    public function index(Request $request): JsonResponse 
    {
        // 認証済みユーザーのロールが"admin"かどうかをチェック
        if (!$request->user()->hasRole('admin')) {
            abort(403, 'アクセスする権限がありません。');
        }
        // ・・・

```
### ③blade内で認可処理を実施する
基本SPA構成が多く、使わないけど念のため
```php
@role('editor')
@endrole

@hasallroles('editor|admin')
@else
@endhasallroles
```
## まとめ
以上でlarave-permissionを利用したロールベースアクセス制御の実装が完了です。今回初めて使用したので、何か気付いたところがあればコメントで教えていただけると嬉しい限りです