🔑

【Laravel】マルチログイン機能を作ってみる(Laravel11, 10)

2023/06/12に公開

はじめに

Laravelを使ったマルチログイン機能を作りましょう。
管理者用のログイン画面を作り、ユーザーとは全く別のログイン機能を作ります。

更新履歴

  • 2024/03/13 Laravel11対応
  • 2023/06/12 公開

環境

  • Laravel10
    • PHP 8.2.3
    • Laravel Framework 10.13.1
  • Laravel11
    • PHP 8.2.16
    • Laravel Framework 11.0.3

設計

画面レイアウト設計

管理ログイン画面

admin-login

管理トップ画面

admin

テーブル定義

Migrationクラスで追加するadminsテーブル定義です。

テーブルレコード

Seederクラスで追加するadminsテーブルのレコードです。

id name password created_at updated_at
1 admin_01 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
2 admin_02 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
3 admin_03 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
4 admin_04 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
5 admin_05 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
6 admin_06 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
7 admin_07 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
8 admin_08 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
9 admin_09 admin 2022-12-30 11:22:33 2022-12-31 23:58:59
10 admin_10 admin 2022-12-30 11:22:33 2022-12-31 23:58:59

データベース

各ファイル作成

Adminモデル及び、関連クラスを作成しましょう。

php artisan make:model Admin -mfs
# 以下が追加される
#   app/Models/Admin.php
#   database/factories/AdminFactory.php
#   database/migrations/YYYY_MM_DD_hhmmss_create_admins_table.php
#   database/seeders/AdminSeeder.php

https://readouble.com/laravel/10.x/ja/eloquent.html

Modelクラス

追加されたAdminモデルの親クラスを\Illuminate\Foundation\Auth\Userクラスに変更しましょう。

app/Models/Admin.php
app/Models/Admin.php
 <?php
 
 namespace App\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Database\Eloquent\Model;
+use Illuminate\Foundation\Auth\User as Authenticatable;
 
-class Admin extends Model
+class Admin extends Authenticatable
 {
     use HasFactory;
 }

テーブル作成準備

Migrationクラス

adminsテーブル定義をMigrationクラスのupメソッドに記述しましょう。

追加済みのMigrationクラス(YYYY_MM_DD_HHmmss_create_admins_table.php)のupメソッドに
nameカラムとpasswordカラムを追加します。

database/migrations/YYYY_MM_DD_HHmmss_create_admins_table.php
database/migrations/YYYY_MM_DD_HHmmss_create_admins_table.php
 <?php
 
 use Illuminate\Database\Migrations\Migration;
 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Support\Facades\Schema;
 
 return new class extends Migration
 {
     /**
      * Run the migrations.
      */
     public function up(): void
     {
         Schema::create('admins', function (Blueprint $table) {
             $table->id();
+            $table->string('name')->unique()->comment('名称');
+            $table->string('password')->comment('パスワード');
             $table->timestamps();
         });
     }
 
     /**
      * Reverse the migrations.
      */
     public function down(): void
     {
         Schema::dropIfExists('admins');
     }
 };

https://readouble.com/laravel/10.x/ja/migrations.html

Seederクラス

adminsテーブルにレコード10件(nameadmin_01admin_10passwordadmin)を追加するよう
Seeder::run メソッドに記述しましょう。

database/seeders/AdminSeeder.php
database/seeders/AdminSeeder.php
 <?php
 
 namespace Database\Seeders;
 
+use App\Models\Admin;
 use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
+use Illuminate\Support\Facades\Hash;
 
 class AdminSeeder extends Seeder
 {
     /**
      * Run the database seeds.
      */
     public function run(): void
     {
         //
+        if (app()->isLocal()) {
+            // 開発環境のみレコードを追加
+            Admin::factory()
+                ->count(10)
+                ->sequence(function ($sequence) {
+                    return [
+                        'name' => sprintf('admin_%02d', $sequence->index + 1),
+                        'password' => Hash::make('admin'), // パスワード: admin  ※ 開発環境用のパスワードのためソース埋め込み
+                        'created_at' => '2022-12-30 11:22:33',
+                        'updated_at' => '2022-12-31 23:58:59',
+                    ];
+                })
+                ->create();
+        }
     }
 }

https://readouble.com/laravel/10.x/ja/seeding.html#writing-seeders
https://readouble.com/laravel/10.x/ja/eloquent-factories.html

database/seeders/DatabaseSeeder.php
database/seeders/DatabaseSeeder.php
 <?php
 
 namespace Database\Seeders;
 
 use App\Models\User;
 // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
 
 class DatabaseSeeder extends Seeder
 {
     /**
      * Seed the application's database.
      */
     public function run(): void
     {
+        $this->call([
+            AdminSeeder::class,
+        ]);
         // User::factory(10)->create();
 
         User::factory()->create([
             'name' => 'Test User',
             'email' => 'test@example.com',
         ]);
     }
 }

https://readouble.com/laravel/10.x/ja/seeding.html#calling-additional-seeders

https://readouble.com/laravel/10.x/ja/seeding.html
https://readouble.com/laravel/10.x/ja/eloquent-factories.html

テーブル作成

MigrationとSeederを実行をしましょう。
これにより、自動的にテーブル・レコードが作成されます。

# Running Migrations
php artisan migrate
# Running Seeders
php artisan db:seed

https://readouble.com/laravel/10.x/ja/migrations.html#running-migrations
https://readouble.com/laravel/10.x/ja/seeding.html#running-seeders

誤ってテーブルが作成してしまった場合

以下のコマンドを実行すると、全てのテーブルを削除し
再度、MigrationとSeederを実行します。

# 全テーブル初期化(全テーブル削除 → Migrations実行 → Seeders実行)
php artisan migrate:fresh --seed

https://readouble.com/laravel/10.x/ja/migrations.html#drop-all-tables-migrate

ガードとプロバイダ設定

config/auth.php ファイルのプロバイダにadmins、ガードにadminを追加しましょう。
これによりauthミドルウェアのadminガード指定が可能になります。

config/auth.php

コメント部分は省略

config/auth.php
 <?php

 return [
     'defaults' => [
         'guard' => env('AUTH_GUARD', 'web'),
         'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
     ],

     'guards' => [
         'web' => [
             'driver' => 'session',
             'provider' => 'users',
         ],
+        'admin' => [
+            'driver' => 'session',
+            'provider' => 'admins',
+        ],
     ],

     'providers' => [
         'users' => [
             'driver' => 'eloquent',
             'model' => env('AUTH_MODEL', App\Models\User::class),
         ],

         // 'users' => [
         //     'driver' => 'database',
         //     'table' => 'users',
         // ],

+        'admins' => [
+            'driver' => 'eloquent',
+            'model' => App\Models\Admin::class,
+        ],
     ],

     'passwords' => [
         'users' => [
             'provider' => 'users',
             'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
             'expire' => 60,
             'throttle' => 60,
         ],
     ],

     'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
 ];

https://readouble.com/laravel/10.x/ja/authentication.html#protecting-routes

https://readouble.com/laravel/10.x/ja/authentication.html

Requestクラス

ログイン情報のバリデーションを行う、フォームリクエストを作成します。

php artisan make:request AdminLoginRequest
app/Http/Requests/AdminLoginRequest.php

以下のauthenticateメソッドはControllerから
手動で呼び出すメソッドとして追加しています。

app/Http/Requests/AdminLoginRequest.php
 <?php
 
 namespace App\Http\Requests;
 
 use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Validation\ValidationException;
 
 class AdminLoginRequest extends FormRequest
 {
     /**
      * Determine if the user is authorized to make this request.
      */
     public function authorize(): bool
     {
-        return false;
+        return true;
     }
 
     /**
      * Get the validation rules that apply to the request.
      *
      * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
      */
     public function rules(): array
     {
         return [
-            //
+            'name' => 'required',
+            'password' => 'required',
         ];
     }
+
+    public function authenticate(): void
+    {
+        if (!Auth::guard('admin')->attempt($this->only('name', 'password'))) {
+            throw ValidationException::withMessages(['failed' => __('auth.failed')]);
+        }
+    }
 }

https://readouble.com/laravel/10.x/ja/validation.html#form-request-validation
https://readouble.com/laravel/10.x/ja/authentication.html#protecting-routes

__('auth.failed') は何をしている?

__は言語ファイルから指定キーのメッセージを取得するヘルパ関数です。
lang/xx/auth.phpファイルのfailedキーのメッセージを取得しています。

https://readouble.com/laravel/10.x/ja/helpers.html#method-__
https://github.com/laravel/framework/blob/v10.13.5/src/Illuminate/Translation/lang/en/auth.php
https://readouble.com/laravel/10.x/ja/localization.html
https://readouble.com/laravel/8.x/ja/auth-php.html

参考

スターターキット Laravel Breeze の\App\Http\Requests\Auth\LoginRequestクラスを参考にしています。

https://github.com/laravel/breeze/blob/v1.21.0/stubs/default/app/Http/Requests/Auth/LoginRequest.php
https://readouble.com/laravel/10.x/ja/starter-kits.html#laravel-breeze

https://readouble.com/laravel/10.x/ja/validation.html

Routing・Middleware・View・Controller

ルート一覧

これから追加すルートの一覧です。
管理トップ画面は、管理ログイン後のみアクセス可能にします。

HTTPメソッド URI ルート名 Controller@method 機能
GET | HEAD admin admin.top - 管理トップ画面🔑
GET | HEAD admin-login admin.login AdminLoginController@create 管理ログイン画面
POST admin-login admin.login.store AdminLoginController@store 管理ログイン
DELETE admin-login admin.login.destroy AdminLoginController@destroy 管理ログアウト

Controllerクラス生成

以下、コマンドでAdminLoginControllerクラスを生成します。

php artisan make:controller AdminLoginController

Routing

ルート一覧通りにroutes/web.phpにルートを追記しましょう。

routes/web.php

以下を追記しましょう。

routes/web.php
// 管理ログイン画面
Route::get('/admin-login', [AdminLoginController::class, 'create'])->name('admin.login');
// 管理ログイン
Route::post('/admin-login', [AdminLoginController::class, 'store'])->name('admin.login.store');
// 管理ログアウト
Route::delete('/admin-login', [AdminLoginController::class, 'destroy'])->name('admin.login.destroy');

// 管理ログイン後のみアクセス可
Route::middleware('auth:admin')->group(function () {
    Route::get('/admin', function () {
        return view('admin.top');
    })->name('admin.top');
});

追記が終わったら、正常に設定できているか確認しましょう。

php artisan route:list

https://readouble.com/laravel/10.x/ja/routing.html
https://readouble.com/laravel/10.x/ja/authentication.html#specifying-a-guard

Middlewareクラス

ゲストアクセス時(ログインしていない場合)に、管理ログイン画面にリダイレクトするように追記します。

app/Http/Middleware/Authenticate.php (Laravel10の場合)
app/Http/Middleware/Authenticate.php
 <?php
 
 namespace App\Http\Middleware;
 
 use Illuminate\Auth\Middleware\Authenticate as Middleware;
 use Illuminate\Http\Request;
 
 class Authenticate extends Middleware
 {
     /**
      * Get the path the user should be redirected to when they are not authenticated.
      */
     protected function redirectTo(Request $request): ?string
     {
+        if (request()->routeIs('admin.*')) {
+            return $request->expectsJson() ? null : route('admin.login');
+        }
         return $request->expectsJson() ? null : route('login');
     }
 }

readouble.com - Laravel 10.x 認証 - 認証されていないユーザーのリダイレクト

bootstrap/app.php (Laravel11の場合)
bootstrap/app.php
 <?php
 
 use Illuminate\Foundation\Application;
 use Illuminate\Foundation\Configuration\Exceptions;
 use Illuminate\Foundation\Configuration\Middleware;
+use Illuminate\Http\Request;
 
 return Application::configure(basePath: dirname(__DIR__))
     ->withRouting(
         web: __DIR__.'/../routes/web.php',
         commands: __DIR__.'/../routes/console.php',
         health: '/up',
     )
     ->withMiddleware(function (Middleware $middleware) {
         //
+        $middleware->redirectGuestsTo(function(Request $request) {
+            if (request()->routeIs('admin.*')) {
+                return $request->expectsJson() ? null : route('admin.login');
+            }
+            return $request->expectsJson() ? null : route('auth');
+        });
     })
     ->withExceptions(function (Exceptions $exceptions) {
         //
     })->create();

readouble.com - Laravel 11.x 認証 - 認証されていないユーザーのリダイレクト

Controllerクラス

AdminLoginControllerクラスにcreatestoredestroyメソッドを追加しましょう。

app/Http/Controllers/AdminLoginController.php
app/Http/Controllers/AdminLoginController.php
 <?php
 
 namespace App\Http\Controllers;
 
+use App\Http\Requests\AdminLoginRequest;
+use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\View\View;
 
 class AdminLoginController extends Controller
 {
-    //
+    /**
+     * ログイン画面
+     */
+    public function create(): View
+    {
+        return view('admin.login');
+    }
+
+    /**
+     * ログイン
+     */
+    public function store(AdminLoginRequest $request): RedirectResponse
+    {
+        $request->authenticate();
+
+        $request->session()->regenerate();
+
+        return redirect()->intended(route('admin.top'));
+    }
+
+    /**
+     * ログアウト
+     */
+    public function destroy(Request $request): RedirectResponse
+    {
+        Auth::guard('admin')->logout();
+
+        $request->session()->invalidate();
+
+        $request->session()->regenerateToken();
+
+        return to_route('admin.login');
+    }
 }

readouble.com - Laravel 10.x 認証 - ユーザーを手作業で認証する
readouble.com - Laravel 10.x 認証 - ログアウト
readouble.com - Laravel 10.x ヘルパ - route()
readouble.com - Laravel 10.x ヘルパ - to_route()

参考

スターターキット Laravel Breeze の\App\Http\Controllers\Auth\AuthenticatedSessionControllerクラスを参考にしています。

https://github.com/laravel/breeze/blob/v1.21.0/stubs/default/app/Http/Controllers/Auth/AuthenticatedSessionController.php
https://readouble.com/laravel/10.x/ja/starter-kits.html#laravel-breeze

https://readouble.com/laravel/10.x/ja/authentication.html

View

admin.login(admin/login.blade.php)と
admin.top(admin/top.blade.php)を追加しましょう。

resources/views/admin/login.blade.php

admin-login

resources/views/admin/login.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>管理</title>
        <style>
            html,
            body {
                height: 100%;
            }

            body {
                margin: 0;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            form {
                text-align: right;
            }
        </style>
    </head>

    <body>
        <main>
            <form method="POST" action="{{ route('admin.login.store') }}">
                @csrf
                <div>
                    <label for="name">Name: </label>
                    <input type="text" id="name" name="name" required />
                </div>
                <div>
                    <label for="password">Password: </label>
                    <input type="password" id="password" name="password" required />
                </div>
                <div>
                    @error('failed')
                        <p style="color:red">{{ $message }}</p>
                    @enderror
                    <button type="submit">ログイン</button>
                </div>
            </form>
        </main>
    </body>
</html>

https://readouble.com/laravel/10.x/ja/routing.html#csrf-protection

resources/views/admin/top.blade.php

admin

resources/views/admin/top.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>管理</title>
        <style>
            html,
            body {
                height: 100%;
            }

            body {
                margin: 0;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            form {
                text-align: right;
            }
        </style>
    </head>

    <body>
        <main>
            @auth('admin')
                <p>ログイン中です。</p>
            @endauth
            <form method="POST" action="{{ route('admin.login.destroy') }}">
                @method('DELETE')
                @csrf
                <button type="submit">ログアウト</button>
            </form>
        </main>
    </body>
</html>

動作確認

以下の動作確認をしましょう。

  • 管理トップ画面(/admin) にアクセス
    • 【expected】 管理ログイン画面(/admin-login) にリダイレクトされる
  • 管理ログイン画面(/admin-login) にて存在しない名称を入力して「ログイン」ボタンクリック
    管理ログイン画面(/admin-login) にて誤ったパスワードを入力して「ログイン」ボタンクリック
    • 【expected】 管理ログイン画面のログインボタンの上に「These credentials do not match our records.」のメッセージが表示される
      (日本語の場合、「ログイン情報が登録されていません。」)
  • 管理ログイン画面(/admin-login) にて正しい情報(name: admin_01, password: admin)を入力して「ログイン」ボタンクリック
    • 【expected】管理トップ画面(/admin) に遷移する
  • 管理トップ画面(/admin) にて「ログアウト」ボタンクリック
    • 【expected】管理ログイン画面(/admin-login) に遷移し、再度、管理トップ画面(/admin) にアクセスしても管理ログイン画面にリダイレクトされる

関連書籍

Discussion