プログラミング

Laravel8 Jetstream fortifyを活用したマルチログイン実装方法

fortify jetstream Laravel PHP 認証
Laravel8 Jetstream fortifyを活用したマルチログイン実装方法

はじめに

Laravel8からは、今までの認証機能がなくなり公式より「Jetstream推奨」と言われるようになりました。単純なログイン機能だけを使いたいのであれば、ライブラリをインストールしてコンパイルするだけで、ほとんどの機能はもちろん、レイアウトもそれなりに使える状態になっているという優れものです。

しかし、開発するシステムによってはマルチログインなど複数の権限を持ったユーザー分けや、ログイン・管理画面の分岐が必要になります。

今回は、Jetstreamの機能を最大限活かしつつ、マルチログイン機能を実装する方法のご紹介です。

Jetstreamで作るマルチログイン

今回のマルチログインでは、以下の3ユーザー分を想定してご紹介します。

  1. 管理者:admin(admins)
  2. 法人:company(companies)
  3. 利用者:user(users)

もし4ユーザー以上必要でも、同じ要領で準備すれば問題なく実装できるので、ご安心ください。

環境準備

まずは環境準備です。今回は必要最低限の工程だけをご紹介します。

Laravelやcomposer自体のインストールが上手くいかない方は、多くの記事で紹介されているので参考にしながらエラーを取り除いていってください。

Laravel8のインストール

Laravel8

まず基盤となるプロジェクト(Laravel8)を作成します。composerを使ったインストールは、プロジェクトを作成するディレクトリで以下のコマンドになります。

composer create-project laravel/laravel {project_name}

{project_name}の部分には、作成するプロジェクト名(フォルダ名)を入れてください。カッコは必要ありません。

自分はテスト用のプロジェクトとして作成するので、Laravel8というプロジェクト名で作成しました。

これで、laravel8のプロジェクトが作成できます。

Jetstreamのインストール

Jetstream

次に今回のメインとなるJetstreamをインストールします。作成したLaravelのディレクトリで以下のコマンドを実行しましょう。

composer require laravel/jetstream

これで、認証で使用するjetstreamのライブラリがインストールできました。

Livewireバージョンのセットアップ

次に、jetstreamのプロジェクトを作成します。jetstreamには、通常版とチーム版があります。今回は通常バージョンを使用します。

※チーム版では、権限の割り当てなどが簡単に実装できるそうです。1ユーザーが複数のチームにも所属できるなど、部屋を作ってそこにユーザーを招待、プロジェクトを作成するといったようなイメージです。

さらに、チーム機能以外にもlivewireとinertiaという2つのバージョンが選択できます。livewireはlaravelではお馴染みのblade、inertiaはvueをフロントとしたものです。

普段からvueを使ったSPAの開発に慣れている人であれば、inertiaのほうが向いているかも知れません。PHPがをメインとしている方は、livewireをおすすめします。

※livewireでは、javascriptで実装したい処理をPHPで記述するようなイメージです。PHPの記述のみで簡単にAPIを使えるなど便利な機能です。

Livewire

今回は通常版且つlivewireのプロジェクトを作成、説明するので以下のコマンドをLaravelプロジェクト直下で実行してセットアップしましょう。

php artisan jetstream:install livewire

これで、jetstreamに必要なファイルがプロジェクト内に作成されました。

データベースの準備

次にデータベースの準備です。Laravelで使用するようのDBを作成、接続情報をenvに記述しておきましょう。

マルチユーザー分のテーブル作成

migrationする前に、初期準備されたusersテーブルを参考に、マルチユーザー分のmigrateファイルを準備します。

php artisan make:migration create_admins_table
php artisan make:migration create_companies_table

カラムはusersテーブル(追加分を含める)と同じ内容で作成します。もしユーザーによって必要なカラムが異なる場合は、実装するシステムに合わせて追加・削除・修正をしてください。

以下、管理者用のmigrateファイルサンプルです。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->text('two_factor_secret')->nullable();
            $table->text('two_factor_recovery_codes')->nullable();
            $table->rememberToken();
            $table->foreignId('current_team_id')->nullable();
            $table->text('profile_photo_path')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admins');
    }
}

usersの内容をコピー、追加分としてtwo_factorのカラム2つを追加しています。

current_team_idはチーム機能を使用しない場合は不要かと思われますので、削除しても良さそうです。

パスワードリセット用テーブルの準備

次に、パスワードリセット用のテーブルも追加で作成します。

php artisan make:migration create_admin_password_resets_table
php artisan make:migration create_company_password_resets_table

内容は、初期状態で準備されているpassword_resets_tableと同様です。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminPasswordResetsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('admin_password_resets', function (Blueprint $table) {
            $table->string('email')->index();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('admin_password_resets');
    }
}

password_resets_tableはusers(利用者)用として残していますが、それぞれ名称をわかりやすくしたい場合は、user_password_resetsというテーブル名に変更しても構いません。migrateファイルを編集する場合は、ファイル名とクラス名、テーブル名にuserという接頭語を忘れず追記しましょう。

以上の準備が整えば、configクリア、migrationを実行してください。

php artisan config:clear
php artisan migration

記述不備がなければ、作成したmigrateファイル分のテーブルが作成されます。

モデルの作成

利用者(User)のモデルは初期準備されていますが、管理者と法人は追加で作成しなければなりません。なので、以下コマンドでファイルを生成、中身をapp/Models/User.phpを参考に変更していきましょう。

php artisan make:model Admin
php artisan make:model Company
管理者用モデル(※参考)
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * 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',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];
}

Userの内容をクラス名以外そのまま貼り付けました。カラムを追加・削除した方は項目に合わせて変更してください。法人用モデル(Company)も同様です。

npmのインストールとコンパイル

データベースとライブラリの準備が整えば、cssとjsのコンパイルです。jetstreamのライブラリをインストールした際に、resourcesへ必要なファイルが生成されていますが、未コンパイル状態です。

まずは、Laravelルートで以下コマンドを実行してnpmのインストールをしましょう。

npm install

その後、必要なライブラリをインストールします。以下コマンドを実行しましょう。

npm install --save-dev postcss-import tailwindcss

この2つが上手く行けば、同ディレクトリで以下コマンドを実行してコンパイルしましょう。

npm run dev

以下のように表示されれば、コンパイル成功です。

成功したら、一度/loginへアクセスして確認してみましょう。上手く行っていれば、jetstreamの初期ログイン画面が表示されます。

これで、初期準備は完了です。

configの設定

マルチログイン用に、configにパラメーターを設定していきます。まずは権限関係の処理として、config/auth.phpへ必要ユーザー分を追記しましょう。

'defaults' => [
    'guard' => 'users',
    'passwords' => 'users',
],

//  ~ 省略

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],
    'admins' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
    'users' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'companies' => [
        'driver' => 'session',
        'provider' => 'companies',
    ],
],

//  ~ 省略

'providers' => [
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ],
    'companies' => [
        'driver' => 'eloquent',
        'model' => App\Models\Company::class,
    ],
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],

//  ~ 省略

'passwords' => [
    'admins' => [
        'provider' => 'admins',
        'table' => 'admin_password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
    'companies' => [
        'provider' => 'companies',
        'table' => 'company_password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
    'users' => [
        'provider' => 'users',
        'table' => 'user_password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
],

defaultsをusersに変更して、guards、providers、passwordsに3ユーザー分を追加しました。

webとapiをそのままにしていますが、不要であれば削除しても構いません。もしapiもマルチユーザー分必要な場合は、api_admins等の名称で用意しましょう。

※もしwebを削除する場合は、config/fortify.php内のguardの値をusersなど存在するguardの値に変更しておきましょう。

また、多用することになるためconfig/fortify.phpの最終行に、作成するマルチユーザーを配列として定義しておきましょう。

// マルチユーザーを定義(任意)
'users' => [
    'admin', 'company', 'user'
],

こちらはあくまで任意の記述です。ユーザー数が多いなどの場合には便利です。もし、管理者のディレクトリは特定されにくい値を使いたいという人は、adminという単語から特定できる(または逆からの特定)ようにしておきましょう。

ルーティングの作成

次にルーティングの作成に取り掛かります。まずは初期機能として以下4ルーティングを準備します。

ログイン画面、ログイン処理、ログアウト、ダッシュボード

URLは一般的な形式として、それぞれのユーザーディレクトリ名をルート直後に置いた、以下のようにしていきましょう。

例)

  • /admin/login → 管理者ログイン
  • /company/logout → 法人ログアウト
  • /user/dashboard → 利用者ダッシュボード

既存のコントローラーを活用

ルーティングの作成に当たって、通常であればコントローラーを作成します。ですが、せっかくjetstreamのライブラリを使用するのに、それぞれのコントローラーを1から作成するとなれば、あまり意味がありません。

Laravel自体が推奨するように、既存のコントローラーが十分カスタマイズに対応しており、強固な認証システムを備えているため、YQUALでは最大限既存のコントローラーを生かした方法をご紹介します。

/**
* ルーティング(管理者・法人・利用者)
*/
foreach(config('fortify.users') as $user){
    Route::prefix($user)
    ->namespace('\Laravel\Fortify\Http\Controllers')
    ->name($user.'.')
    ->group(function () use($user) {
        /**
        * ログイン 画面
        * @method GET
        */
        Route::name('login')->middleware('guest')
        ->get('/login', 'AuthenticatedSessionController@create');
        /**
        * ログイン 認証
        * @method POST
        */
        Route::name('login')->middleware(['guest', 'throttle:'.config('fortify.limiters.login')])
        ->post('/login', 'AuthenticatedSessionController@store');
        /**
        * ログアウト
        * @method POST
        */
        Route::name('logout')->middleware('guest')
        ->post('/logout', 'AuthenticatedSessionController@destroy');
        /**
        * ダッシュボード
        * @method GET
        */
        Route::name('dashboard')->middleware(['auth:'.\Str::plural($user), 'verified'])
        ->get('/dashboard', function () use($user) {
            return view($user.'.dashboard');
        });
    });
}

グループ化した4つのルーティングをforeachで3ユーザー分作成しています。routeファイルを分けている場合は、それぞれに必要なルーティングを記述してもOKです。ログイン処理については、各ユーザー同じように必要になることが多いため、今回はこのように一括で定義、middlewareを割り振りました。

わかるように、それぞれで使用するコントローラーはjetstreamのものです。以下の場所に初期ルーティングが記述されているため、それを参考に必要最低限のものを呼び出しています。

/vendor/laravel/fortify/routes/routes.php

コントローラーは同じくライブラリ内のHttp/Controllersにあります。それぞれどのメソッドが呼び出されているかを確認すれば、どのタイミングでどのようにカスタマイズをすれば良いかわかります。

※カスタマイズするために、ライブラリ内のファイルの変更はNGです。確認時に間違って上書きしてしまわないように気をつけてください。

ビューの作成

次に、ログイン画面で使用するためのviewの準備します。これは、各ユーザー分必要になりますが、もし共有できるパーツがあれば、細分化して使い回しても良いでしょう。

authの複製

jetstreamをインストールした際に、resources/views内にauthというフォルダが生成されます。これがログイン時に使用するviewで、ダッシュボードは同階層にdashboard.blade.phpがあるのでこれを活用します。

それぞれのユーザーディレクトリを作成し、その中にそれらを配置しましょう。

  • admin/auth/…
  • admin/dashboard.blade.php
  • company/auth/…
  • company/dashboard.blade.php
  • user/auth/…
  • user/dashboard.blade.php

navigation-menu.blade.phpなどはlivewireで読み込まれているため、ディレクトリを変更するとlivewireを作成しなければ機能しません。なので、もしそのままを活用する場合は注意してください。

レイアウトは1から自分で作成、または別のテンプレートなどを使用する場合は、それぞれ使用するものをユーザーディレクトリ内へ配置してください。

ルートの変更

次に、ファイル内に記述されているルートの変更です。管理者ログインで使用するadmin/auth/login.blade.phpであれば、リクエスト先をroute(‘admin.login’)のように、先程作成したルートへ変更してください。

また、どのユーザーのログイン画面か判別・正常に読み込めているかどうかを判別できるように、タイトルを入れておくと良いでしょう。

以下サンプルとして、管理者ログイン画面のbladeファイルです。

<x-guest-layout>
    <x-jet-authentication-card>
        <x-slot name="logo">
            <x-jet-authentication-card-logo />
        </x-slot>
        <h2 class="font-semibold text-xl text-gray-800 leading-tight mb-4">
            管理者 ログイン
        </h2>
        <x-jet-validation-errors class="mb-4" />

        @if (session('status'))
            <div class="mb-4 font-medium text-sm text-green-600">
                {{ session('status') }}
            </div>
        @endif

        <form method="POST" action="{{ route('admin.login') }}">
            @csrf

            <div>
                <x-jet-label for="email" value="{{ __('Email') }}" />
                <x-jet-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
            </div>

            <div class="mt-4">
                <x-jet-label for="password" value="{{ __('Password') }}" />
                <x-jet-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="current-password" />
            </div>

            <div class="block mt-4">
                <label for="remember_me" class="flex items-center">
                    <x-jet-checkbox id="remember_me" name="remember" />
                    <span class="ml-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
                </label>
            </div>

            <div class="flex items-center justify-end mt-4">
                @if (Route::has('admin.forgot'))
                    <a class="underline text-sm text-gray-600 hover:text-gray-900" href="{{ route('admin.forgot') }}">
                        {{ __('Forgot your password?') }}
                    </a>
                @endif

                <x-jet-button class="ml-4">
                    {{ __('Login') }}
                </x-jet-button>
            </div>
        </form>
    </x-jet-authentication-card>
</x-guest-layout>

先程作成したルーティングではパスワード忘れのルートは作成していませんが、ログイン画面で必要な場合は合わせて準備しておくと良いでしょう。

今回のサンプルでは、admin.forgotという名称でルートを定義しています。

※標準の状態でRoute::hasによるチェックが入っているため、ルーティング未定義でもエラーは出ないようになっています。

ServiceProviderへの追記

画面とルーティングが準備できましたが、アクセスしてもタイトルが表示されないため、表示されているのは初期ログイン画面だということがわかります。これは、コントローラーを既存のものを使用しているためです。これらをカスタマイズするために、ServiceProviderを活用します。

今回は認証周りを作成するため、jetstreamによって追加されたFortifyServiceProviderに追記していきましょう。

追記するのは、bootメソッドの一番下です。このboot内で呼び出している処理がログイン処理に関係しているため、これより前に追記してしまうとログイン自体が上手くいきません。もし新しくカスタマイズ用のServiceProviderを作成・登録する場合は、FortifyServiceProvider以降に読み込む用にconfig/app.phpへ追加しなければなりません。

今回は、カスタマイズの処理をまとめるため、カスタマイズ用のメソッドをboot内で呼び出します。

<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // 
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Fortify::createUsersUsing(CreateNewUser::class);
        Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
        Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);

        RateLimiter::for('login', function (Request $request) {
            return Limit::perMinute(5)->by($request->email.$request->ip());
        });

        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });

        // マルチログイン用のカスタマイズ(注:上記より下に記述)
        $this->multiLoginCustomize();
    }

    /**
     * マルチログインのカスタマイズ用メソッド
     * @return void
     */
    private function multiLoginCustomize()
    {
        // ここにカスタマイズの処理を記述
    }
}

ログイン画面用viewの変更

まずは、ログイン画面で使用するviewの切り替えについてです。これは、Fortifyというクラスで定義されており、変更が可能です。今回は各ユーザーに振り分けられたauthを使用するため、viewPrefixを変更することで読み込んで欲しいviewへ振り分けることができます。

リクエストされたURLを判定して、それに合わせたディレクトリへ振り分ければ良いので、パスからユーザーを判別、viewPrefixを書き換えましょう。

/**
 * マルチログインのカスタマイズ用メソッド
 * @return void
 */
private function multiLoginCustomize()
{
    // urlからユーザーを取得
    $user = \Str::of(\Request::path())->before('/');
    if(in_array($user, config('fortify.users'))){
        // FortifyのviewPrefixを書き換え(各ユーザー用viewを使用)
        Fortify::viewPrefix($user.'.auth.');
    }
}

これで、/admin/loginへリクエストされた際にはadmin/auth/login.blade.phpを使用、/company/loginへリクエストされた際には、company/auth/login.blade.phpへと切り替えることができます。

もし、読み込むbladeファイル名自体別のものを使用場合、viewPrefixだけでは指定することができません。ですがコントローラーからviewを返却する際にResponseというクラスを使用しているため、これをsingletonで別のクラスを渡してやることで変更することができます。

この内容は、後ほど「ログアウト先の変更」で解説するため、参考にしてください。

configの上書き

次に認証を行う際の分岐についてです。どのguardを使うかは、今までコントローラーで指定することが多かったでしょう。しかし、jetstreamではconfigで設定されたguardの値を受け取り、認証しようとしています。これはconfig/fortify.phpのguardで設定されています。1ユーザーのみの認証であれば問題ありませんが、今回のようにマルチログインをする場合はこの値を動的に変更しなければなりません。

なので、先程作成したviewPrefix用の分岐にconfigの上書き処理を追加します。

/**
 * マルチログインのカスタマイズ用メソッド
 * @return void
 */
private function multiLoginCustomize()
{
    // urlからユーザーを取得
    $user = \Str::of(\Request::path())->before('/');
    if(in_array($user, config('fortify.users'))){
        // FortifyのviewPrefixを書き換え(各ユーザー用viewを使用)
        Fortify::viewPrefix($user.'.auth.');
        // 権限ページに合わせたguardの切り替え
        \Config::set('fortify.guard', \Str::plural($user));
        // ダッシュボードの切り替え
        \Config::set('fortify.home', '/'.$user.RouteServiceProvider::HOME);
    }
}

\Config::setを使用して、それぞれの指定を上書きしています。guardの他にもダッシュボードのURLもconfigの値を参照しているため、こちらもリダイレクトさせたいパスに上書きしましょう。

これで、マルチログイン用のユーザー切り替えのための準備は完了です。

初期ルーティングの削除

ログイン処理が作成できれば、初期搭載されているログイン等のルーティングが不要になる場合もあるでしょう。これらは、同じくServiceProviderへ記述することで削除することができます。今回であれば、3ユーザーのログイン画面への分岐ができれば、初期の/login等は不要ですね。

ルーティングの無効化は、以下のようにregisterメソッドで呼び出します。

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // デフォルトのFortifyルーティングを無効化(任意)
        Fortify::ignoreRoutes();
    }

これと同じく、Jetstreamのルーティングを無効化することもできます。ただし、これらはログイン関係の一連の処理が完成してからにすることをオススメします。なぜなら、初期ルーティングがviewなどに残っていればルート不定のエラーが多く発生してしまうからです。

Middlewareの修正

次に、Middlewareをマルチログイン用に修正します。

リダイレクト先の分岐

例えば、未ログインユーザーが管理者ページへのアクセスをしようとした際にはリダイレクトする必要があります。標準状態では、/loginへリダイレクトするため、もしこちらのログイン画面を使用しない場合はそれぞれのユーザーのログイン画面やルートなど任意の場所へ移管する必要があります。これらは以下のミドルウェアを修正しましょう。

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            // 未認証
            $user = \Str::of($request->path())->before('/');
            if(strval($user)){
                return route($user.'.login');
            }
            return route('welcome');
        }
    }
}

今回は、ユーザーが特定できれば、ユーザーに合わせたログイン画面へ移管するようにしています。もし判定できない場合は、標準搭載されているwelcome画面へ飛ばしています。

これと同じように、ログイン済みユーザーを管理画面へ飛ばすためのリダイレクト処理も修正しましょう。

<?php

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @param  string|null  ...$guards
     * @return mixed
     */
    public function handle(Request $request, Closure $next, ...$guards)
    {
        $guards = empty($guards) ? [null] : $guards;
        foreach ($guards as $guard) {
            if (Auth::guard($guard)->check() && $guard) {
                // ログイン済みユーザーの管理画面へのリダイレクト処理(マルチログイン)
                return redirect(\Str::singular($guard).'/'.RouteServiceProvider::HOME);
            }
        }

        return $next($request);
    }
}

以上で、未ログインまたはログイン済みユーザーのリダイレクト処理の分岐は完成です。

ログイン機能の確認

それでは、実際にログイン機能自体が正常に機能するかどうかを確認しましょう。jetstreamではユーザー登録機能が標準で付いており、それを使っても構いませんが、マルチログインの場合はそもそも不要であったり、管理者に関しては不要などといった事情もあるため、わざわざ作らなくても良いことが多いでしょう。

なので、今回は先にログイン処理自体が上手く実装できているかを確認するために、シーダーを使って作成しましょう。

Seederによるユーザー登録

今回は3ユーザーのマルチログイン機能を作成したため、各1ユーザーずつシーダーを使って登録します。

php artisan make:seeder AdminSeeder
php artisan make:seeder CompanySeeder
php artisan make:seeder UserSeeder

以下、管理者ユーザーサンプルです。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class AdminSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        \DB::table('admins')->insert([
            'name' => 'YQUAL',
            'email' => 'info@s-yqual.com',
            'password' => \Hash::make('admin'),
        ]);
    }
}

他のユーザーも同じ要領で作成しましょう。想定どおりのユーザーで認証ができているかどうかを判別する意味も含め、パスワードまたはメールアドレス等の認証で使用する値は変えておきましょう。

Seederファイルが準備できれば、登録用として追記して、Seeder登録コマンドをLaravelルートで実行しましょう。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call([
            AdminSeeder::class,
            CompanySeeder::class,
            UserSeeder::class,
        ]);
    }
}
php artisan db:seed

ユーザーの取り込みが終われば、実際にログイン処理を試してみましょう。

無事ログインすることができました。

同様に、法人・利用者のアカウントでもそれぞれログインすることができました。

ダッシュボードのタイトルは、ログイン画面と同様にそれぞれのユーザーが判別できるようにタイトル変更をしています。

ログアウト先の変更

ログイン処理が完成したので、最後にログアウト機能を実装しましょう。まず、リクエスト先のルートを、それぞれのユーザー用のログアウトへ変更します。

もし、標準のフレームを使用している場合は、3ユーザーで同じファイルを使用しているため、guardでリクエスト先を分岐させるなどしてください。

今回は、簡易処理としてURLからユーザーを判別してルートのnameを指定しています。

// ログアウト処理routeの一部 ※4箇所有り
<!-- Authentication -->
<form method="POST" action="{{ route(\Str::of(\Request::path())->before('/').'.logout') }}">
    @csrf

    <x-jet-responsive-nav-link href="{{ route(\Str::of(\Request::path())->before('/').'.logout') }}"
                   onclick="event.preventDefault();
                    this.closest('form').submit();">
        {{ __('Logout') }}
    </x-jet-responsive-nav-link>
</form>

これだけでも問題なくログアウトすることができます。標準ではルートへのログアウトが指定されているため、Laravel8のwelcome画面が表示されるはずです。ですが、今回はユーザーに合わせてログアウト後の画面を、それぞれのログイン画面へ移管させましょう。

singletonの活用

ここで使用するのが、singletonです。もしログイン画面のviewを標準のものから大きく変更する場合などにもこの機能を使用することになります。

まず、ログアウト用のレスポンスを以下のディレクトリからコピーして、app/Http/Responses内に配置しましょう。

※Responsesディレクトリはなければ作成してください

/vendor/laravel/fortify/src/Http/Responses/LogoutResponse.php
 ↓ コピー
/app/Http/Responses/LogoutResponse.php

では、コピーしてきたLogoutResponse.phpの内容を以下の様に修正しましょう。

<?php

namespace App\Http\Responses;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Laravel\Fortify\Contracts\LogoutResponse as LogoutResponseContract;

class LogoutResponse implements LogoutResponseContract
{
    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function toResponse($request)
    {
        // 権限によるルート分岐
        if($user = \Str::of($request->path())->before('/')){
            $route = route($user.'.login');
        }else{
            $route = route('welcome');
        }
        return $request->wantsJson()
                    ? new JsonResponse('', 204)
                    : redirect($route)
                    ->with('status', 'ログアウトしました');
    }
}

今まで作成した容量と同じ様に分岐を作成、ログアウト後にリダイレクトさせるルートを分岐させています。今回は、ログアウト時のメッセージを表示させるために、withでstatusにメッセージを追加しました。こちらは任意です。

では、コピーしてきたLogoutResponseを代わりに呼び出してもらうよう、configの上書き等と同様にFortifyServiceProviderへ記述しましょう。

use Laravel\Fortify\Contracts\LogoutResponse;

class FortifyServiceProvider extends ServiceProvider
{

--省略

/**
 * マルチログインのカスタマイズ用メソッド
 * @return void
 */
private function multiLoginCustomize()
{
    // urlからユーザーを取得
    $user = \Str::of(\Request::path())->before('/');
    if(in_array($user, config('fortify.users'))){
        // FortifyのviewPrefixを書き換え(各ユーザー用viewを使用)
        Fortify::viewPrefix($user.'.auth.');
        // 権限ページに合わせたguardの切り替え
        \Config::set('fortify.guard', \Str::plural($user));
        // password_resetテーブルの切り替え
        \Config::set('fortify.passwords', \Str::plural($user));
        // ダッシュボードの切り替え
        \Config::set('fortify.home', '/'.$user.RouteServiceProvider::HOME);
    }
    // ログアウト画面の切り替え
    $this->app
    ->singleton(LogoutResponse::class, function ($app) {
        return new \App\Http\Responses\LogoutResponse;
    });
}

クラス定義前にライブラリのLogoutResponseをuseしておき、boot内でsingletonを使ってログアウト画面を切り替えています。

では、実際に管理者ダッシュボードからログアウトしてみましょう。

ログアウト後、管理者ログイン画面へリダイレクトされ、withで付与したメッセージが表示されました。

これで、jetsreamを使ったマルチログイン処理の完成です。

まとめ

いかがだったでしょうか。

今回は「Laravel8 Jetstream + fortifyを活用したマルチログインの実装方法」についてご紹介しました。

ログインからログアウトまでの最低限の流れだけのご紹介となりましたが、どのようにカスタマイズをしていけば良いのかがわかれば、他の機能も同じ要領でカスタマイズすることができます。

二段階認証や、メールアドレスの確認なども付いているため、フルで活かす場合は1からコントローラーやアクションを作成せず、既存の機能をしっかり活用してレスポンス先など必要最低限のカスタマイズすればすぐに複雑且つ強固な認証システムを利用することができるようになります。

これからLarvel8、Jetstreamを使ったシステム開発に取り組もうと考えている方は、ぜひ参考にしてみてくださいね。

【Laravel】1対1リレーションをわかりやすく解説(belongsTo)

【Laravel】論理削除対応型existsバリデーションの実装方法

Laravel7系でTraitのmakeコマンドを作成する方法

注目の記事

数字が増える毎日投稿テクニック【銀の弾丸はありません】
ライティング
YouTuber,ブロガー,銀の弾丸
数字が増える毎日投稿テクニック【銀の弾丸はありません】

  まずは以下のグラフを御覧ください。     私が管理しているYouTubeのチャンネルで、1日投稿ができない日がありました。 そうすると、その日がいつなのかすぐわかるぐらい露骨に視聴者数が減ったのです。それほど、毎日投稿することは結果に大きく影響します。   今回は、本ブログで何度も切り口を変え...

PHPポケモン「わざ編〜わるあがき〜」35
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「わざ編〜わるあがき〜」35

わるあがき そろそろ技のPPを実装段階にきたので、その前に「わるあがき」という技を作成しましょう。「わるあがきって何?」という人のために、どういった技なのか簡単に説明します。 全ポケモン使用可能。ただし、レベルアップなどで普通のわざとして覚えることはできず、自分の技がすべて選択不能になった場合...

たった2日で200万円!フリーランスが簡単に仕事を受注できる方法とは
マーケティング
コロナ,フリーランス,ホームページ制作,助成金
たった2日で200万円!フリーランスが簡単に仕事を受注できる方法とは

  仕事の依頼が全然来ない・・・ 営業しても話を聞いてくれない・・・   その多くが営業力以上に、営業をすべきタイミングがわかっていない人がほとんどです。 実は助成金等を活用することで、行政書士などの職業以外でも仕事を受注することは可能になります。   今回は筆者が、たった2日で200万...

トレーナー戦編 トレーナー情報の作成 PHPポケモン 97
プログラミング
PHP,PHPポケモン,ポケモン
トレーナー戦編 トレーナー情報の作成 PHPポケモン 97

トレーナー戦 いよいよPHPポケモンでもトレーナー戦の実装に取り掛かっていきます。バトルシステム自体は野生ポケモンと同じですが、トレーナーバトルでは以下の項目が追加、または制限を設けることになります。 複数匹のポケモン 逃げられない 捕まえられない 賞金   複数匹のポケモン ざっくり...

PHPポケモン「バトルシステム編〜努力値の獲得〜」33
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「バトルシステム編〜努力値の獲得〜」33

努力値の実装 今回はポケモンのやりこみ要素の一つ、努力値システムを導入します。既に努力値の項目は「ピカチュウで学ぶオブジェクト指向」の段階で実装し、ステータス計算にも判定済みですが、肝心な「努力値を獲得する仕組み」自体は出来ていませんでした。なので、バトルシステムも終盤となったこのタイミングで...

config実装編(ドット記法・多次元配列) PHPポケモン 71
プログラミング
PHP,PHPポケモン,ポケモン
config実装編(ドット記法・多次元配列) PHPポケモン 71

configファイルの作成 プログラミングでは設定値というものを使うことが良くあります。量が多い場合はデータベースへ格納して管理する場合も多いですが、わざわざテーブルを用意してまで格納するほどのものでなければ、ファイルに配列として定義してアクセスできる方が便利です。フレームワークではこれらをconfigフ...

トークン認証とサニタイズ編 PHPポケモン 38 コード配布あり
プログラミング
PHP,PHPポケモン,ポケモン
トークン認証とサニタイズ編 PHPポケモン 38 コード配布あり

構成の見直し PHPポケモンも38回となり、大分作り込みが出来てきました。ここ最近はコードの説明ばかりでデモページなども準備出来ていませんでしたが、それには內部側の問題点が多かったためです。今回はその辺りをキレイに解決できるよう、本格的な構成の見直しをします。   ちなみにですが、どれぐらいの見直...

ポケモン図鑑編 PHPポケモン 88
プログラミング
PHP,PHPポケモン,ポケモン
ポケモン図鑑編 PHPポケモン 88

ポケモン図鑑とは ポケモンに出会ったり、仲間にしたりすると、ポケモン図鑑のデータがどんどんと埋まっていきます。PHPポケモンでもこの仕組みを実現させるために、ポケモン図鑑を作成していきましょう。   クラスによる管理 ポケモン図鑑はクラス管理をしていきます。プレイヤー1人に対して1つのポケモン図...

カテゴリ

SEO対策 イベント デザイン ネットワーク ビジネスモデル フリーランス プログラミング マーケティング ライティング 動画編集 雑記

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach fortify function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jetstream jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss SEO Sequel Pro Skype SNS SSH Symfony TCP/IP title Toastr Trait Twig Twitter UCC V系 WAN WebSub Wi-Fi wiki Windows WordPress XAMPP xml Xserver YouTube YouTuber Zoom アーティスト アウトプット アクセス層 アニメーション アフィリエイト イーブイ インターネット インプット エンジニア オブジェクト指向 お金配り クリック単価 クリック数 コミュニケーション能力 コロナ コンサルティング サムネイル システムエンジニア スタートアップ スタイルシート スパム データベース ディープフェイク デザイナー デザイン テレワーク ナンパ ニュース ネットワークモデル ノマドワーク バナー ピカチュウ ビジネス フィード フリーランス ブロガー ブログ プログラマー プログラミング プログラミング学習 プログラミング教育 プロトコル ホームページ制作 ポケモン マークアップ マーケティング メール リモートワーク レンダリング 三井住友 三宮 仕事依頼 児童デイ 児童発達支援 公開鍵 初心者 助成金 勉強法 営業 広告 広告収入 必勝マニュアル 放課後等デイサービス 朝活 楽天 深層学習 無線LAN 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 認証 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力