Laravel 6 で管理者だけがユーザーを追加する系の仕組みに認証をカスタマイズする

歳とともにスポーツの上達がガンガン鈍っていることに絶賛絶望中のまついです。

プログラミングはある程度順調に成長していることがせめてもの救いです。

さて、タイトルにもある通り、Laravel の認証をカスタマイズして、管理者だけがユーザーを追加する系のウェブアプリを作る方法をまとめてみました。

なるべくシンプルに、かつ Laravel 6 に対応した方法を考えました。

# 要件

管理者だけがユーザーを追加できるという状況を作るために、標準状態では困る部分に絞ってリストアップ。

- ユーザー登録ページには管理者だけがアクセス可能
- 管理者もユーザーのマイページ的なものを見る必要がある
- ユーザー作成後に自動ログインされるのは困る
- ログイン中のユーザーを適切にリダイレクトしたい

こんなところでしょうか。

# コード

というわけでコードを先に見ていただきましょう。

```web.php
Route::get('/', function () {
    return view('welcome');
});

Route::view('login', 'auth.login')->name('login');

Route::group(['middleware' => ['auth', 'can:admin']], function () {
    Route::get('admin', 'GateController@admin');
    Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
    Route::post('register', 'Auth\RegisterController@register')->name('register');
});

Route::group(['middleware' => ['auth', 'can:user']], function () {
    Route::redirect('/user', '/login', 301);
    Route::get('user/{user}', 'GateController@user');
});

Auth::routes(['register' => false]);
```

まずはルーティング用のファイルです。

アクセス制限の方法としてはごく普通のコードですね。

ポイントは ```Auth::routes``` の引数でユーザー登録のルーティング無効化。登録時のルーティングをカスタマイズするのでこれだけ記入する必要があります。

次に、コントローラーを見ていきましょう。

web.php に書いたとおり、GateController と RegisterController を呼び出しています。

GateController は新規に作成します。

```GateController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class GateController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function admin()
    {
        return view('home');
    }

    public function user() {
        return view('home');
    }
}
```

認証後の動きをここで定義しますが、今回は管理者、ユーザーともに home.blade.php を表示させています。

このあたりは要件によって変えてください。

次に、RegisterController を見てみましょう。

```Auth/RegisterController.php

protected $redirectTo = '/login';

protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
        'role' => $data['role'] ?? 5,
    ]);
}

protected function register(Request $request, $user) {
    $this->validator($request->all())->validate();

    event(new Registered($user = $this->create($request->all())));

    // $this->guard()->login($user);

    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}
```

全部載せると長いので一部抜粋。

まずは ```$redirectTo = '/login'``` でユーザー作成後のリダイレクト先を指定します。リダイレクト先をログインページにしている理由は、後ほど掲載するログインページで認証済みの場合の処理に関係してくるのです。

次に、```create``` メソッドに Role を追加。今回はデフォルト値を設定してあります。

そして、```register()``` メソッド。これは Illuminate/Foundation/Auth/RegistersUsers.php の ```register()``` メソッドを上書き。

RegisterUsers.php には ```registered()``` という空のメソッドがあって、いかにも上書きしろと誘ってきますが、今回はスルー。

内容的にはメソッドをコピペして、```$this->guard()->login($user);``` をコメントアウトしています。

動き的にはこのログイン処理、```registered()``` にあるべきな気がするんですが、どうなんでしょうね。。。

続きましては、AuthServiceProvider.php です。

```Providers\AuthServiceProvider.php
const ADMIN_ROLE = 10;
const USER_ROLE = 5;

public function register()
{
    //
}

public function boot()
{
    $this->registerPolicies();

    Gate::define('admin', function ($user) {
        return $user->role == self::ADMIN_ROLE;
    });

    Gate::define('user', function ($user) {
        return $user->role == self::USER_ROLE;
    });
}
```

最初にRoleの値を決めて、定数に入れます。

今回、管理者を10、ユーザーを5にしていますが、このへんはお好きなようにどうぞ。

そして、Gateの定義をするのですが、今回は管理者とユーザーの2種類あるのでそれぞれ定義。

ここは特にややこしいこともないですね。

さあ、次で最後になります。Middleware にある RedirectIfAuthenticated.php。こいつを見ていきましょう。

```Middleware/RedirectIfAuthenticated.php
const ADMIN_ROLE = 10;
public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)->check()) {
        $redirect = Auth::user()->role == ADMIN_ROLE ? 'admin' : 'user/'.Auth::user()->name;
        return redirect('/'.$redirect);
    }

    return $next($request);
}
```

訪問者が認証済みの場合の動きを定義しています。管理者の場合は /admin それ以外は /user/{username} にリダイレクトといった感じ。

Roleの値はいくつかの場所で使うので、コンフィグかなにかに書いておいたほうがいいですね。

そんなわけで、ネットで探してもドンピシャのものが見つからなかったので自分用メモとして書いてみました。

こういう構成は多分、それほど需要が無いか、開発者が独自で実装していて表に情報が出ていないかということなんでしょうかね。

Laravelで似たようなことをしたい人のお役に立てたら嬉しいです。

では。