Laravel9でマルチログイン機能を作成してみる



マルチログイン機能というのは複数のユーザー区分があって、それぞれモデルとテーブルがあって、それぞれ独立してログインできるような機能を指しています。(ユーザーと管理者など)


Laravelでマルチログイン機能を実現するには今のところ、通常の認証機能をコピーして作成するしかないようです。(現在2022年8月)

BreezeやJetstreamなどLaravelにはログイン認証用のライブラリがあるようですが、laravel/uiを使用しようと思います。




※Laravelのインストールなどはお済の前提で進めさせていただきます。




概要

以下の手順で進めていきたいと思います。

⓪laravel/uiを導入(未導入の場合)
①ログインユーザーを追加(config/auth.php)
②ログイン後のリダイレクト先を設定(app/Http/Middleware/RedirectIfAuthenticated.php)

③ログインしてない時のリダイレクト先を設定(app/Exceptions/Handler.php)
④テーブルの作成(migration)
⑤モデルの作成
Illuminate/Foundation/Auth/Userをextendsする必要がある
コントローラ・ビューの作成(登録・ログイン・ホーム。コピーして作る)
⑦ルーティング
⑧特別編:パスワード再設定


今回新しく作るユーザーはadminとします。


⓪laravel/uiを導入(未導入の場合)

すでに導入済みの場合は飛ばしてください。

composer require laravel/ui
php artisan ui bootstrap --auth
npm install && npm run dev


2行目の箇所は、reactやvueを使っている場合は下記のようにするといいようです。

php artisan ui react --auth  #reactを使っている場合
php artisan ui vue --authn   #vueを使っている場合



php artisan ui ○○をすると、コントローラーとviewが作成されます。
それぞれauthディレクトリの中に作成されます。
これはデフォルトのuser用のものなので、後々これをコピーして第2ユーザ(admin)用の機能を機能を作成していきます。

/registerにアクセスすると、user用の登録画面が開かれると思います。



①ログインユーザーを追加(config/auth.php)

下記3つを設定します。すべてconfig/auth.phpに追記します。

・認証管理の方法(セッションかトークンか)
・モデルとログイン方法(eloquentかdatabase)の指定
・パスワード再設定で使用するテーブルなどの指定

認証管理の方法(セッションかトークンか)

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'user' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [                //追記
        'driver' => 'session',  //追記
        'provider' => 'admins', //追記
    ],                          //追記
],

モデルとログイン方法(eloquentかdatabase)の指定

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
    'admins' => [                            //追記
        'driver' => 'eloquent',              //追記
        'model' => App\Models\Admin::class,  //追記
    ],                                       //追記
],

パスワード再設定で使用するテーブルなどの指定

'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
        'throttle' => 60,
    ],
    'admins' => [                           //追記
        'provider' => 'admins',             //追記
        'table' => 'password_resets',       //追記
        'expire' => 60,                     //追記
        'throttle' => 60,                   //追記
    ],                                      //追記
],




②認証済みの場合のリダイレクト先(app/Http/Middleware/RedirectIfAuthenticated.php)

認証済みの時にログインページにアクセスするとすべてhomeに飛ばされるようになっています。
adminで認証済みの状態で、admin/loginにアクセスした時はadmin/homeに飛ばしたいので、その設定を行います。

ファイルはapp/Http/Middleware/RedirectIfAuthenticated.phpです。

public function handle(Request $request, Closure $next, ...$guards)
{
    $guards = empty($guards) ? [null] : $guards;
    foreach ($guards as $guard) {
        if($guard == "admin" && Auth::guard($guard)->check()) {   //追記
            return redirect('admin/home');                        //追記
        }                                                         //追記
        if (Auth::guard($guard)->check()) {
            return redirect(RouteServiceProvider::HOME);
        }
    }
    return $next($request);
}





③認証してない時のリダイレクト先を設定(app/Exceptions/Handler.php)

上記とは逆で、認証してない状態で認証が必要なページ(homeなど)にアクセスするとログインページに飛ばされるようになっています。
認証してない状態でadmin/homeにアクセスした時はadmin/loginに飛ばしたいので、その設定を行います。

ファイルはapp/Exceptions/Handler.phpです。

protected function unauthenticated($request,Throwable $exception)
{
    if($request->expectsJson()) {
        return respomse()->json(['message' => $exception->getMessage()],401);
    }
    if($request->is('admin') || $request->is('admin/*')){  //追記
        return redirect()->guest('/admin/login');      //追記
    }                              //追記
    return redirect()->guest($exception->redirectTo ?? route('login'));
}





④テーブルの作成(migration)

通常通りです。カラムは、名前・メールアドレス・パスワードと最低限にしています。

php artisan make:migration create_admins_table
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->rememberToken();                                //追記
        $table->timestamps();   
    });
}
php artisan migrate





⑤モデルの作成Illuminate/Foundation/Auth/Userをextendsする必要がある

Adminモデルを作成して下記のように修正します。
普通のモデルとの違いはIlluminate/Foundation/Auth/Userを継承するところです。

php artisan make:model Admin
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User;
use Illuminate\Notifications\Notifiable;

class Admin extends User
{
    use HasFactory, Notifiable;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}




コントローラ・ビューの作成(登録・ログイン・ホーム。コピーして作る)

ここまで作れば、あとは自動で生成されているファイルをコピーしながらコントローラ・ビューを作成していくだけです。
登録・ログイン・ホームを作成していきます。

登録

app/Http/Controllers/Auth/RegisterController.phpをコピーして、app/Http/Controllers/admin/RegisterController.phpとして作成して、下記のように修正します。

namespace App\Http\Controllers\admin;           //修正

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\Models\Admin;                           //修正
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;            //追記

class RegisterController extends Controller
{
    use RegistersUsers;


    protected $redirectTo = '/admin/home';      //修正


    public function __construct()
    {
        $this->middleware('guest:admin');       //修正
    }


    protected function guard()                  //追記
    {                                           //追記
        return Auth::guard('admin');            //追記
    }                                           //追記


    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:admins'],     //修正
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    
    protected function create(array $data)
    {
        return Admin::create([                  //修正
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}

resources/views/auth/register.blade.phpをコピーしてresources/views/admin/register.blade.phpを作成します。
Formの送信先を変更します。

<form method="POST" action="{{ url('admin/register') }}">    //修正
    @csrf



ログイン

app/Http/Controllers/Auth/LoginController.phpをコピーしてapp/Http/Controllers/admin/LoginController.phpを作成して、下記のように修正します。

namespace App\Http\Controllers\admin;                       //修正

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;                        //追記

class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $redirectTo = '/admin/home';                  //修正


    public function __construct()
    {
        $this->middleware('guest:admin')->except('logout'); //修正
    }


    protected function guard()                              //追記
    {                                                       //追記
        return Auth::guard('admin');                        //追記
    }                                                       //追記
}

resources/views/auth/login.blade.phpをコピーして
resources/views/admin/login.blade.phpを作成します。
Formの送信先を変更します。
パスワード再設定の遷移先も変更します。

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

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@if (Route::has('password.request'))                                        //削除
    <a class="btn btn-link" href="{{ url('admin/password/request') }}">     //修正
        パスワードを忘れた方はこちら
    </a>
@endif                                                                      //削除


ホーム

resources/views/home.blade.phpをコピーしてresources/views/admin/home.blade.phpを作成します。
読み込むレイアウトを変更します。

@extends('layouts.admin')   //修正


resources/views/layouts/app.blade.phpをコピーして、resources/views/layouts/admin.blade.phpを作成します。
登録ボタン・ログインボタン・ログアウトボタンの遷移先を変更します。

@guest
    <li class="nav-item">
        <a class="nav-link" href="{{ url('admin/login') }}">{{ __('Login') }}</a> //修正
    </li>
    <li class="nav-item">
        <a class="nav-link" href="{{ url('admin/register') }}">{{ __('Register') }}</a> //修正
    </li>
@else
    <li class="nav-item dropdown">
        <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
            {{ Auth::user()->name }}
        </a>
        <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
            <a class="dropdown-item" href="{{ route('admin/logout') }}"     //修正
                onclick="event.preventDefault();
                                document.getElementById('logout-form').submit();">
                {{ __('Logout') }}
            </a>
            <form id="logout-form" action="{{ url('admin/logout') }}" method="POST" class="d-none"> //修正
                @csrf
            </form>
        </div>
    </li>
@endguest





⑦ルーティング

routes/web.phpに下記を追記します。

Route::view('/admin/login', 'admin/login');
Route::post('/admin/login', [App\Http\Controllers\admin\LoginController::class, 'login']);
Route::view('/admin/register', 'admin/register');
Route::post('/admin/register', [App\Http\Controllers\admin\RegisterController::class, 'register']);
Route::view('/admin/home', 'admin/home')->middleware('auth:admin');






これで第2ユーザー(ここではadmin)の登録・ログイン・ホームへのアクセスができるようになったと思います。





⑧特別編:パスワード再設定

パスワード再設定は、上記の登録ログインとはやり方が異なるので、別で解説させていただきます。
/password/resetにアクセスすると、パスワード再設定が行えると思います。
これをコピーして第2ユーザー用にパスワード再設定機能を作成します。

概要

⓪メール設定
①コントローラ(ForgotPasswordControllerとResetPasswordController)
②ビュー(passwords/email.blade.phpとpasswords/reset.blade.php)
③パスワードリセットのリンクURLを設定(モデルとNotification)
④ルーティング

⓪メール設定

すでに設定済みの場合は飛ばしてください。
.envファイルで設定します。下記ではテストツールのmailhogを利用してますが、通常のメールサーバーも指定できます。

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=user
MAIL_PASSWORD=password
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"

①コントローラ(ForgotPasswordControllerとResetPasswordController)

app/Http/Controllers/Auth/ForgotPasswordController.phpをコピーして、app/Http/Controllers/admin/ForgotPasswordController.phpを作成して修正します。

namespace App\Http\Controllers\admin;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Password;        //追記

class ForgotPasswordController extends Controller
{
    use SendsPasswordResetEmails;

    protected function broker()                 //追記
    {                                           //追記
        return Password::broker('admins');      //追記
    }                                           //追記
}   

app/Http/Controllers/Auth/ResetPasswordController.phpをコピーして、app/Http/Controllers/admin/ResetPasswordController.phpを作成して修正します。

namespace App\Http\Controllers\admin;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;            //追記
use Illuminate\Http\Request;                        //追記

class ResetPasswordController extends Controller
{
    use ResetsPasswords;

    protected $redirectTo = 'admin/home';           //修正

    protected function broker()                     //追記
    {                                               //追記
        return Password::broker('admins');          //追記
    }                                               //追記
}

②ビュー(passwords/email.blade.phpとpasswords/reset.blade.php)

resources/views/auth/passwords/email.blade.phpをコピーして、
resources/views/admin/passwords/email.blade.phpを作成して遷移先を修正します。

<form method="POST" action="{{ url('admin/password/email') }}">    //修正
    @csrf

resources/views/auth/passwords/reset.blade.phpをコピーして、
resources/views/admin/passwords/reset.blade.phpを作成して遷移先を修正します。

<form method="POST" action="{{ url('admin/password/reset') }}">    //修正
    @csrf

③パスワードリセットのリンクURLを設定(モデルとNotification)

app/Models/Admin.phpに下記を追記します。

use App\Notifications\ResetPasswordNotification;            //追記

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

public function sendPasswordResetNotification($token)       //追記
{                                                           //追記
    $url = url("admin/password/reset/$token");              //追記
    $this->notify(new ResetPasswordNotification($url));     //追記
}                                                           //追記


コマンドでNotificationを作成して修正します。

php artisan make:notification ResetPasswordNotification
namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class ResetPasswordNotification extends Notification
{
    use Queueable;

    public function __construct($url)   //修正
    {
        $this->url = $url;              //修正
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('The introduction to the notification.')
            ->action('Notification Action', "$this->url?email=$notifiable->email")  //修正
            ->line('Thank you for using our application!');
    }
}

④ルーティング

Route::view('/admin/password/reset', 'admin/passwords/email');
Route::post('/admin/password/email', [App\Http\Controllers\admin\ForgotPasswordController::class, 'sendResetLinkEmail']);
Route::view('/admin/password/reset/{token}', [App\Http\Controllers\admin\ResetPasswordController::class,'showResetForm']);
Route::post('/admin/password/reset', [App\Http\Controllers\admin\ResetPasswordController::class, 'reset']);




以上になります!

コメント

タイトルとURLをコピーしました