LaravelのGateを使ってルーティングにアクセスコントロールを実装する

## はじめに

LaravelでのWebアプリケーションの開発をしていて、ログイン中のユーザーのロール毎にアクセス可能なルーティングのコントロールを実装する必要がでてきました。
Laravelの認可の機能である、ゲートを使った簡単なルーティングへのアクセスコントロールを実装したので、やったことを書いておきます。
なお、ここではゲートをルーティングに実装する基本のみで、ログイン認証の実装などには触れません。

## やりたかったこと

以下、簡単なプロジェクト管理システムを開発している前提で進めます。

ログインユーザーのロールは4種類あります。
- 管理者
- リーダー
- マネージャー
- メンバー

それぞれのロール毎に使える機能を制限したかったので、アクセスできるURIを設定するのがやりたいことでした。
ロール毎にアクセス可能なURIは次の表の様になります。

|URI|機能|管理者|リーダー|マネージャー|メンバー
|---|---|---|---|---|---|
|/manage_member|メンバーの管理|○|×|×|×
|/add_project|プロジェクトの追加|○|○|×|×
|/edit_project|プロジェクトの編集|○|○|○|×
|/view_project|プロジェクトの閲覧|○|○|○|○

ログインユーザー毎にロールをもたせるためにrolesテーブルとusersテーブルを次の様に設計しました。

## テーブル設計

### roles テーブル
|id|name|
|---|---|
|1|管理者
|2|リーダー
|3|マネージャー
|4|メンバー

### users テーブル
|id|name|role_id
|---|---|---
|1|管理者山田|1
|2|リーダー鈴木|2
|3|マネージャ伊藤|3
|4|メンバー加藤|4

## ゲートの記述

ここでは、特定のルーティングにアクセスする許可があるかどうかの定義をします。定義には、ログイン中ユーザーの`role_id`で判別します。

```app/Providers/AuthServiceProvider.php

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // メンバーの管理ができるロール
        Gate::define('manage_member', function (User $user) {
            // ゲートは常に最初の引数にユーザーインスタンスを受け取ります。
            return $user->role_id === 1; // 管理者
        });
    }
```

この記述をすることで、Laravelのミドルウェアで`canミドルウェア`を使った認可をすることができます。
次のコードが`canミドルウェア`をLaravelのルーティング`routes/web.php`で使う例になります。

```routes/web.php
    // このルーティングには`メンバーの管理ができるロール`のみアクセスできる
    Route::group(['middleware' => 'can:manage_member'], function () {
        Route::get('/manage_member', 'MemberController@manage');
    });
```

続きのコードも書いていきます。

```app/Providers/AuthServiceProvider.php

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        // メンバーの管理ができるロール
        Gate::define('manage_member', function (User $user) {
            return $user->role_id === 1; // 管理者
        });

        // プロジェクトの追加ができるロール
        Gate::define('add_project', function (User $user) {
            return in_array($user->role_id, [1, 2], true); // 管理者とリーダー
        });

        // プロジェクトの編集ができるロール
        Gate::define('edit_project', function (User $user) {
            return in_array($user->role_id, [1, 2, 3], true); // 管理者とリーダー、マネージャー
        });

        // プロジェクトの閲覧ができるロール
        Gate::define('view_project', function (User $user) {
            return in_array($user->role_id, [1, 2, 3, 4], true); // 管理者とリーダー、マネージャー、メンバー
        });

    }
```

## 定義したゲートをルーティングで使う

```routes/web.php
    // このルーティングには`メンバーの管理ができるロール`のみアクセスできる
    Route::group(['middleware' => 'can:manage_member'], function () {
        Route::get('/manage_member', 'MemberController@manage');
    });

    // このルーティングには`プロジェクトの追加ができるロール`のみアクセスできる
    Route::group(['middleware' => 'can:add_project'], function () {
        Route::get('/add_project', 'ProjectController@add');
    });

    // このルーティングには`プロジェクトの編集ができるロール`のみアクセスできる
    Route::group(['middleware' => 'can:edit_project'], function () {
        Route::get('/edit_project', 'ProjectController@edit');
    });

    // このルーティングには`プロジェクトの閲覧ができるロール`のみアクセスできる
    Route::group(['middleware' => 'can:view_project'], function () {
        Route::get('/view_project', 'ProjectController@view');
    });
```

許可のないロールでアクセスした場合、ミドルウェアは403ステータスコード（Forbidden/アクセス拒否）を返します。

## おわりに

以上がゲートを使ったルーティングへの認可の実装でした。
Laravelの認可には、ルーティング以外にもモデルやアクション単位で認可のロジックを導入する仕組みがありますので、詳しくは公式ドキュメント [Laravel 5.7 認可](https://readouble.com/laravel/5.7/ja/authorization.html) をご覧ください。