OHTA412

Laravel6でマルチログイン機能を実装する方法

「管理者」と「ユーザー」のように、複数のログイン機能を実装する方法です。usersテーブルにユーザーを登録して、adminsテーブルに管理者を登録します。認証機能もそれぞれ別にします。

ログイン機能を同じにして各ユーザーに権限を与えることで表示する内容を分ける場合は、こちらの記事を参考にしてください。

laravel/uiパッケージを使い認証機能をインストールする

今回はLaravel6を使うので、6系をインストールします。「blog」の部分は、必要に応じて変更してください。

$ composer create-project --prefer-dist laravel/laravel blog "6.*"

インストールしたLaravelのディレクトリ内で、laravel/uiパッケージをインストールします。Laravel6はlaravel/uiのバージョン2以降に対応していないので、バージョン1をインストールします。

$ composer require laravel/ui "^1.0" --dev

laravel/uiパッケージがインストールできたら、下記コマンドで認証機能を構築します。認証に必要なルートとビューが生成されます。

$ php artisan ui vue --auth

これで認証機能が構築されました。続いて、下記コマンドでフロントエンド関連のファイルをビルドします。ここでNode.jsが必要になるので、PCにインストールされていない場合はNode.jsをインストールしてください。

$ npm install && npm run dev

publicにアクセスして画面の右上にLOGINとREGISTERが表示されていれば、認証機能のインストールが完了しています。

Adminモデルを作る

Userモデルはユーザーの認証に使うので、管理者用に下記コマンドでAdminモデルを作ります。adminsテーブルも作るので、マイグレーションも一緒に作成します。コマンドの最後に「-m」を付けるとマイグレーションも作ってくれます。

今回は一般的な開発にならって、Modelsフォルダ内にモデルを格納します。そのため「Models/Admin」とします。

$ php artisan make:model Models/Admin -m

User.phpを移動する

コマンドを実行すると、app/Models内にAdmin.phpが作られます。app直下にいるUser.php(Userモデル)もModelsフォルダに移動させます。それに応じて、User.phpのnamespaceを変更します。

// app/Models/User.php:2行目付近

namespace App\Models;

User.phpのディレクトリが変わったので、Laravel内でUser.phpを読み込んでいる所の記述を変更します。「App\User」で全検索し、該当箇所を「App\Models\User」へ変更します。

Admin.phpを変更する

User.phpの内容をコピーしてAdmin.phpの内容を書き換えます。class名を「User」から「Admin」へ変更し、$fillableと$hidden以外は使用しないので削除します。

// app/Models/Admin.php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;

class Admin extends Authenticatable
{

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

マイグレートする

Adminモデルと一緒にマイグレーションも作成したので、そのファイルを修正します。テーブル構成はusersテーブルと同じにするので、up()メソッドはcreate_users_table.phpの内容を複製します。

// database/migrations/20xx_xx_xx_xxxxxx_create_admins_table.php

…略…

class CreateAdminsTable extends Migration
{
    …略…
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->bigIncrements('id');
            // ここから追加
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            // ここまで追加
            $table->timestamps();
        });
    }
    …略…
}

.envファイルにデータベース情報を記述します。下記コードはXAMPPを利用した場合の例です。

// .env:9行目付近

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=root
DB_PASSWORD=

修正できたら、下記コマンドでマイグレーションを実行します。

$ php artisan migrate

シーダーでユーザー情報を登録する

シーダーを登録しておくと、本番環境にデプロイしたときに同じユーザーを簡単に使用できます。その都度登録する場合は、このセクションを飛ばしても問題ありません。

下記コマンドで、adminsテーブルのシーダーファイルを作成します。

$ php artisan make:seeder AdminsTableSeeder

app/database/seeds内にAdminsTableSeeder.phpというシーダーファイルが作られるので、それを編集します。12行目付近にrun()メソッドが定義されているので、中身を記述します。

// database/seeds/AdminsTableSeeder.php:12行目付近

public function run()
{
    DB::table('admins')->insert([
        'name' => 'admin',
        'email' => 'admin@test.com',
        'password' => Hash::make('pass1234'),
    ]);
}

上記コードは、adminsテーブルに管理者情報を登録する例です。名前やメールアドレスを連想配列形式で記述します。パスワードはHashファサードを使って暗号化させます。

修正できたら、そのファイルを読み込ませるために、同階層にあるDatabaseSeeder.phpを変更します。こちらも12行目付近にrun()メソッドが定義されています。UsersTableSeeder用の記述がコメントアウトしてあるので、それにならってAdminsTableSeeder用のコードを記述します。

// database/seeds/DatabaseSeeder.php:12行目付近

public function run()
{
    $this->call(AdminsTableSeeder::class);
}

下記コマンドで、シーディングを実行します。データベースのadminsテーブルに、先ほど設定した管理者情報が登録されていれば成功です。

$ php artisan db:seed

ガードとプロバイダを登録する

configフォルダ内にあるauth.phpに、Admin用のガード(guard)とプロバイダ(provider)を登録します。ガードは、ユーザーを認証する方法を定義しています。プロバイダは、認証するユーザーを取得する方法を定義しています。

これらを設定することで、新しく認証方法を定義できます。

// config/auth.php

…略…
'guards' => [
    …略…
    // ここから追加
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
    // ここまで追加
    …略…
],

'providers' => [
    …略…
    // ここから追加
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ],
    // ここまで追加
    …略…
],
…略…

ガード(guard)の設定

上記コードの例では、7行目でadminという名前でガードを登録しています。そのガードには、driverとprividerが設定してあります。

driverでは認証の種類を選択でき、sessionを選択すれば、セッションストレージとクッキーを使って認証を行います。tokenを選択すれば、リクエストで受け取ったAPIトークンとユーザーに紐づけられたトークンが一致するかどうかで認証を行います。

providerでは、認証するユーザー情報をどのように取得するか設定できます。9行目でadminsとしていますが、これはその下のprovidersで定義しているadminsを指しています。

プロバイダ(privider)の設定

上記ガードで指定したadminsプロバイダは、18行目で定義しています。プロバイダではユーザー情報の取得方法を定義していますが、driverとmodelの2項目を設定しています。

driverはガードの設定でも出てきましたが、それとは別物です。ユーザー情報を取得する方法のことですが、eloquentとクエリビルダから選択できます。今回はeloquentを選択しています。

modelの項目では、どのモデルを使用するのか設定します。modelsフォルダのAdminモデルを指定します。

ログイン画面を作る

ユーザー用のログイン画面はすでにできているので、管理者用のログイン画面を作ります。resources/views内にadminsフォルダを作って、管理者に関するページはその中に入れるようにします。

resources/views/auth内にあるlogin.blade.phpをコピーして、resources/views/admins内に複製します。formタグのaction属性のルーティングをloginからadmin.loginに変更します。ルーティングの設定は後ほど行います。

// resources/views/admins/login.blade.php:11行目付近

<form method="POST" action="{{ route('admin.login') }}"> // この行を変更
    …略…
</form>

ログイン後の画面を作る

先ほど作ったlogin.blade.phpと同階層に、home.blade.phpを作ります。ログイン後、このページにリダイレクトさせます。

今回は、resources/views直下にあるhome.blade.phpを、そのままadminsフォルダ内に複製します。ファイル名は、index.blade.phpにしておきます。必要に応じて内容を変更してください。

管理者用のコントローラーを作る

管理者用のコントローラーを、AdminControllerという名前で作ります。app/Http/Controllers/Auth内に認証関連のコントローラーがすでに用意されているので、今回はこの中にAdminControllerも一緒に入れます。

$ php artisan make:controller Auth/AdminController

AdminControllerができたら、下記コードを追記します。

// app/Http/Controllers/Auth/AdminController.php

…略…

class AdminController extends Controller
{
    // ここから追加
    public function __construct()
    {
        $this->middleware('auth:admin');
    }

    public function index()
    {
        return view('admins.home');
    }
    // ここまで追加
}

8行目でコンストラクタ関数を記述し、middlewareを定義しています。13行目でindex()メソッドを記述し、管理者としてログインしたときのトップページのviewを設定します。先ほど作ったhome.blade.phpです。

その他、管理者がアクセスするページがある場合は、ここに追記していきます。

認証関連のコントローラーを作る

先ほど作ったAdminControllerと同じところに、認証関連のコントローラーを作ります。下記コマンドで、AdminLoginControllerという名前で作成します。

$ php artisan make:controller Auth/AdminLoginController

AdminLoginControllerができたら、下記コードを追記します。ユーザー用のLoginControllerをベースにして、管理者用に多少手を加えたものです。LoginControllerはAuthenticatesUsersトレイトも読み込んでいるので、そこの記述も参考にします。

// app/Http/Controllers/Auth/AdminLoginController.php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
// ここから追加
use Illuminate\Validation\ValidationException;
use Auth;
// ここまで追加

class AdminLoginController extends Controller
{
    // ここから追加
    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout');
    }

    public function showLoginForm()
    {
        return view('admins.login');
    }

    public function login(Request $request)
    {
        // ログイン時のバリデーション
        $request->validate([
            $this->username() => 'required|string',
            'password' => 'required|string',
        ]);

        if (Auth::guard('admin')->attempt(['email' => $request->email, 'password' => $request->password], $request->remember)) {
            // ログインが成功したとき
            return redirect()->intended(route('admin.index'));
        }

        // ログインが失敗したとき
        return $this->sendFailedLoginResponse($request);
    }

    protected function sendFailedLoginResponse(Request $request)
    {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ]);
    }

    public function username()
    {
        return 'email';
    }

    public function logout()
    {
        Auth::guard('admin')->logout();
        return redirect()->route('admin.login');
    }
    // ここまで追加
}

15行目でコンストラクタ関数を記述し、middlewareを定義しています。20行目のshowLoginForm()メソッドで、ログインページのviewを設定します。

25行目のlogin()メソッドには、ログインしたときの処理が書いてあります。バリデーションを行い、ログインが成功したら管理者用のトップページへリダイレクトし、失敗したらauth.failedのメッセージが返されます。

54行目では、ログアウト時の処理が書いてあります。

ルーティングを設定する

管理者用認証に関するルーティングの設定を行います。

// routes/web.php

Route::prefix('admin')->group(function () {
    Route::get('/login', 'Auth\AdminLoginController@showLoginForm')->name('admin.login');
    Route::post('/login', 'Auth\AdminLoginController@login')->name('admin.login');
    Route::post('/logout', 'Auth\AdminLoginController@logout')->name('admin.logout');
    Route::get('/', 'Auth\AdminController@index')->name('admin.index');
});

未認証ユーザーのリダイレクト先を設定する

未認証ユーザー(ログインしていないユーザー)が認証が必要なページにアクセスしたとき、ユーザーのログインページにリダイレクトされます。しかし、管理者ページにアクセスしたときは、管理者のログインページにリダイレクトさせる必要があります。

その設定をするために、app/Http/Middleware内のAuthenticate.phpを下記コードのように修正します。

// app/Http/Middleware/Authenticate.php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;
// ここから追加
use Illuminate\Support\Facades\Route;
// ここまで追加

class Authenticate extends Middleware
{
    …略…
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            // ここから削除
            return route('login');
            // ここまで削除
            // ここから追加
            if (Route::is('user.*')) {
                return route('login');
            } elseif (Route::is('admin.*')) {
                return route('admin.login');
            }
            // ここまで追加
        }
    }
}

redirectTo()メソッドの中身を書き換えて、ルートの名前からリダイレクト先を条件分岐しています。ルート名がuserから始まっている場合はユーザーのログイン画面にリダイレクトし、adminから始まっている場合は管理者のログイン画面にリダイレクトします。

認証ユーザーのリダイレクト先を設定する

認証ユーザー(ログインしているユーザー)がログインページにアクセスしたとき、ログイン時のトップページにリダイレクトされます。この処理も先ほどと同じように、ユーザーと管理者で処理を分岐させる必要があります。

その設定をするために、app/Http/Middleware内のRedirectIfAuthenticated.phpを下記コードのように修正します。

// app/Http/Middleware/RedirectIfAuthenticated.php:19行目付近

public function handle($request, Closure $next, $guard = null)
{
    // ここから削除
    if (Auth::guard($guard)->check()) {
        return redirect(RouteServiceProvider::HOME);
    }
    // ここまで削除
    // ここから追加
    switch ($guard) {
        case 'admin':
            if (Auth::guard($guard)->check()) {
                return redirect()->route('admin.index');
            }
            break;

        default:
            if (Auth::guard($guard)->check()) {
                return redirect()->route('home');
            }
            break;
    }
    // ここまで追加

    return $next($request);
}

「ログインしていた場合、RouteServiceProvider::HOMEにリダイレクトする」という処理がif文で書いてありますが、これを削除します。代わりに、adminの場合とその他の場合でリダイレクト先を分岐させるswitch文を追加します。

まとめ

以上で、マルチログイン機能が実装できます。admin/login/にアクセスすれば、管理者用のログイン画面が表示されます。login/にアクセスすれば、ユーザー用のログイン画面が表示されます。

今回はユーザーの他に管理者だけ追加しましたが、同じ手順で複製すれば好きなだけログイン機能を追加することができます。