✌️

Laravel10.x のインストールからBreezeを使ったマルチ認証まで

2023/12/16に公開

最新のLaravelをインストールして、最低限やること、入れるコンポーネントとかを備忘録しようと思う。バージョンごとに変わったりするので、記録は大事。
基本は、laravelドキュメントを参考にしています。
環境はWindows11, Xampp, PHP8.1.6, MariaDB10.4.24, Visual Studio Code(vscode)

インストール

まずLaravelプロジェクトを作成します。最新のLaravelをDLする前提です。
Xamppのコントロールパネルで、Shellボタンを押してShellを立ち上げ、次のコードを入力します。
project-name には、自身のプロジェクト名を入力します。

composer create-project laravel/laravel project-name

インストールが終わったらvscodeで該当プロジェクトを開き、新規ターミナルを立ち上げます。

Laravelの初期設定

サイトURL・メール・データベース等の設定と、サーバー時間の修正・日本語で使うための設定が必要です。

ENVファイルの修正

まずENVファイルの修正から行います。

APP_NAMEにはサイト名を。日本語や、スペースのある文字を入れるときは""ダブルクォーテーションで囲んで入力します。
APP_ENVはlocalでローカル開発用、productionで本番用となります。
APP_URLはサイトのトップURL。
DB_と書かれたところには、使用するDBの設定を入力します。
MAIL_と書かれたところには、使用するメーラー情報を入力します。

app.phpの修正

次にconfigフォルダにあるapp.phpの設定です。サーバーの時間と言語を設定します。

confg\app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
'faker_locale' => 'ja_JP',

言語ファイル

上記の設定が終わったら、言語ファイルの作成とストレージLinkの設定をします。
このコマンドでアプリケーションにlangディレクトリを作成します。
デフォルトはenで作成されますが、コピーしてjaにリネームします。

php artisan lang:publish

langファイル直下にja.jsonファイルも置くこと。
下記のjson.jsonをja.jsonにリネームして使用します。ありがたや。

https://github.com/Laravel-Lang/lang/tree/main/locales/ja
jaファイル

ファイルストレージ

laravelは、デフォルトでファイルをstorage/app/publicに保存します。そのフォルダにWebからアクセスできるようにするため、ストレージのシンボリックリンクを作成します。
これで、publicフォルダの中にstorageフォルダへのショートカットが作成されます。

php artisan storage:link

最後に初期データベースのマイグレーションをして、サーバーが立ち上がるか確認します。

php artisan migrate
php artisan serve

Breezeのインストール

laravel8.xから、認証機能にBreezeとJetstreamというスターターキットが導入されました。
Jetsreamにはteamサポートがあり、それを使用してSNSのような招待制やWordPressのユーザーグループのようなユーザー分けが可能です。
一方、Breezeは最小限の認証機能だけで、teamサポートのような機能は自分で作成する必要があります。ただ、Breezeのほうが、Jetstreamよりはるかにカスタマイズしやすく感じたので、こちらを使用してマルチ認証を作成したいと思います。

今回はUser+Adminの形で増やします。
もっと簡単な実装方法もあるのですが、Roleなどをふやせるような実装方法のほうが、あとあと便利なのでBreezeでいきます。
ログイン、ユーザー登録、パスワードリセット、メール確認、パスワード確認が標準でついています。

Breeze本体のインストール

ターミナルに下記を入力します。

composer require laravel/breeze --dev

終ったら次にこれを入力します。

php artisan breeze:install

エンターを押すと、色々質問されます。自分のお好みで選択してください。
私はBlade, dark mode support, PHPunitの選択をしました。
終わると次を入力します。
新しいデータベーステーブルのマイグレーションを行います。

php artisan migrate
npm install
npm run dev

これでViteが起動します。

別のターミナルを立ち上げてサーバーを起動してください。

php artisan serve

アドレスバーにhttp://localhost:8000 と入力するとLaravelのwelcome画面が表示されます。
右上にLoginとRegisterの表示が出ていれば成功です。
これでBreezeのインストールは終わりです。あとは修正を行います。

Adminの作成

現在Breezeは、userの登録・削除・編集ができるようになっています。このUserのシステムを利用して、Adminも使えるようにしたいと思います。

Model作成

Adminモデルの作成時に、-mコマンドをつけることで、同名のデータベーステーブルも作成できます。

php artisan make:model Admin -m

これでModelsフォルダにAdmin.phpができたので、User.phpの中身を丸ごとコピーし書き換えます。

app\Models\Admin.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\Sanctum\HasApiTokens;

class Admin extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

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

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
}

Adminテーブル作成

databaseフォルダの中にmigrationsフォルダがあります。
その中にcreate_admins_tableと書かれたphpファイルがあるので、それを編集します。
create_users_tableの中身をコピーして適時、書き換えます。

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')->nullable();
            $table->string('email')->nullable();
            $table->string('login_name');
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

パスワード変更用テーブル作成

パスワードの変更もできるようにターミナルでテーブルを作成します。

php artisan make:migration create_admin_password_reset_tokens_table

これもUser用のpassword_reset_tokensテーブルの中身をコピーして貼り付けます。

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('admin_password_reset_tokens', function (Blueprint $table) {
            $table->string('email')->primary();
            $table->string('token');
            $table->timestamps();
        });
    }

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

Migration

全て終わったら、ターミナルでマイグレーションします。

php artisan migrate

ルートの設定

現在、userのルートは設定されていますが、admin用のルートは無いので、独自に設定します。

Route作成

routes/web.phpをコピーしてadmin.phpにリネームします。
auth.phpの中身をコピーして、admin.phpに貼り付け、適時修正します。
use文のPathは、Authの後にAdminをいれ、Routeのmiddlewareにはauth:adminsを記入します。

routes\admin.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\Admin\AuthenticatedSessionController;
use App\Http\Controllers\Auth\Admin\ConfirmablePasswordController;
use App\Http\Controllers\Auth\Admin\EmailVerificationNotificationController;
use App\Http\Controllers\Auth\Admin\EmailVerificationPromptController;
use App\Http\Controllers\Auth\Admin\NewPasswordController;
use App\Http\Controllers\Auth\Admin\PasswordController;
use App\Http\Controllers\Auth\Admin\PasswordResetLinkController;
use App\Http\Controllers\Auth\Admin\RegisteredUserController;
use App\Http\Controllers\Auth\Admin\VerifyEmailController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

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

Route::middleware('guest')->group(function () {
    Route::get('register', [RegisteredUserController::class, 'create'])
        ->name('register');

    Route::post('register', [RegisteredUserController::class, 'store']);

    Route::get('login', [AuthenticatedSessionController::class, 'create'])
        ->name('login');

    Route::post('login', [AuthenticatedSessionController::class, 'store']);

    Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
        ->name('password.request');

    Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
        ->name('password.email');

    Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
        ->name('password.reset');

    Route::post('reset-password', [NewPasswordController::class, 'store'])
        ->name('password.store');
});

Route::middleware('auth:admins')->group(function () {
   Route::get('/dashboard', function(){
	   return view('admin.dashboard');
   })->middleware('verified')->name('dashboard');

    Route::get('verify-email', EmailVerificationPromptController::class)
        ->name('verification.notice');

    Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
        ->middleware(['signed', 'throttle:6,1'])
        ->name('verification.verify');

    Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
        ->middleware('throttle:6,1')
        ->name('verification.send');

    Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
        ->name('password.confirm');

    Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);

    Route::put('password', [PasswordController::class, 'update'])->name('password.update');

    Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
        ->name('logout');
});

ルートプロバイダ

appフォルダのProviderフォルダにRouteServiceProvider.phpがあります。
Adminにも対応できるように変更します。

app\Providers\RouteServiceProvider.php
namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * The path to your application's "home" route.
     *
     * Typically, users are redirected here after authentication.
     *
     * @var string
     */
    public const HOME = '/dashboard';
    public const ADMIN_HOME = '/admin/dashboard';//追加

    /**
     * Define your route model bindings, pattern filters, and other route configuration.
     */
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });

        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));

	  //ここから追加
            Route::prefix('admin')->as('admin.')
                ->middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/admin.php'));
	    //ここまで

            Route::middleware('web')
                ->group(base_path('routes/web.php'));
        });
    }
}

Auth認証のガードの設定

Laravelの認証では、AuthファザードのGuardを参照して認証ユーザーを区別します。
middlewareではguard設定で指定されたものだけを認証します。

auth設定

configフォルダのauth.phpの中を参照していますので、そこに指定します。

config\auth.php
return [
    'defaults' => [
        'guard' => 'users',
        'passwords' => 'users',
    ],
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
    ],
     'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
        'admins' => [
            'provider' => 'admins',
            'table' => 'admin_password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],
    'password_timeout' => 10800,

];

Middleware

appフォルダのHttpフォルダの中にMiddlewareがあります。
ここのファイルで指定された挙動を行うことになっています。
まず認証時の Authenticateファイルを修正します。
routeにadmin.の文字列があったらadmin.loginに、それ以外はloginに飛ばす処理になります。

app\Http\Middleware\Authenticate.php
namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;//追加

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     */
    protected function redirectTo(Request $request): ?string
    {
        //return $request->expectsJson() ? null : route('login');
        if ($request->expectsJson()) {
            return null;
        } else {
            if (Route::is('admin.*')) {
                return route('admin.login');
            } else {
                return route('login');
            }
        }
    }
}

次に認証後のAuthenticatedでどこにリダイレクトするかの処理になります。
RedirectIfAuthenticatedファイルを編集します。
guardがadminsでrouteにadmin.の文字列があれば、先ほど設定したADMIN_HOMEにリダイレクトする設定にします。

app\Http\Middleware\RedirectIfAuthenticated.php
namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$guards): Response
    {

        $guards = empty($guards) ? [null] : $guards;

        foreach ($guards as $guard) {
            if (Auth::guard($guard)->check()) {
                if (($guard === 'admins') && $request->routeIs('admin.*')) {
                    return redirect(RouteServiceProvider::ADMIN_HOME);
                }

                return redirect(RouteServiceProvider::HOME);
            }
        }

        return $next($request);
    }
}

Requestにguard追加

appフォルダのHttpフォルダの中、Requestフォルダにログインの際の条件を記載したLoginRequestファイルがあります。
ここのauthenticate条件にGuardも追加します。これで、Adminはadmins、UserはusersのGuardが無いとログインできなくなります。

app\Http\Request\Auth\LoginRequest.php
//前略
    /**
     * Attempt to authenticate the request's credentials.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function authenticate(): void
    {
        $this->ensureIsNotRateLimited();

        //追加
        if ($this->routeIs('admin.*')) {
            $guard = 'admins';
        } else {
            $guard = 'users';
        }

    //guardを追加
        if (! Auth::guard($guard)->attempt($this->only('email', 'password'), $this->boolean('remember'))) {
            RateLimiter::hit($this->throttleKey());

            throw ValidationException::withMessages([
                'email' => trans('auth.failed'),
            ]);
        }

        RateLimiter::clear($this->throttleKey());
    }

【Admin用にカスタマイズしたいとき】
Adminは、ログイン名とパスワードでログインしたいというときは、LoginRequest.phpをコピーして、AdminLoginRequest.phpにリネームして、emailの部分をlogin_name等に書き換えます。
ログイン時に、Auth\Admin\AuthenticatedSessionController.phpを使用するので、その中のLoginRequestをAdminLoginRequestに書き換えればOKです。
あとは、Adminログイン用のViewファイルのフォームの内容を適時書き換えします。

Admin専用AuthController

現在、AuthはUserモデル専用のcontrollerになっています。Adminモデルにも同じようにcontrollerを作成します。
appフォルダのHttpフォルダにControllersがあり、その中にAuthフォルダがあります。
Authフォルダの中にAdminフォルダを作成して、Authの全ファイルをAdminフォルダにコピーしてください。
そしてすべてのファイルのnamespace・use文・viewファイルを適時書き換えます。
viewファイルは、admin.を追加でつけます。(resources/views/authの中にadminを作成予定)
guardはadmins。
下記は一例です。

Auth\Admin\AuthenticatedSessionController.php
namespace App\Http\Controllers\Auth\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

class AuthenticatedSessionController extends Controller
{
    /**
     * Display the login view.
     */
    public function create(): View
    {
        return view('auth.admin.login');
    }

    /**
     * Handle an incoming authentication request.
     */
    public function store(LoginRequest $request): RedirectResponse
    {
        $request->authenticate();

        $request->session()->regenerate();

        return redirect()->intended(RouteServiceProvider::ADMIN_HOME);
    }

    /**
     * Destroy an authenticated session.
     */
    public function destroy(Request $request): RedirectResponse
    {
        Auth::guard('admins')->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        return redirect('/admin/login');
    }
}

RegisteredUserControllerの場合。use App\Models\User; になってるところはAdminにします。

Auth\Admin\RegisteredUserController.php
namespace App\Http\Controllers\Auth\Admin;

use App\Http\Controllers\Controller;
use App\Models\Admin;
use App\Models\AdminRole;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
use Symfony\Contracts\Service\Attribute\Required;

class RegisteredUserController extends Controller
{
    /**
     * Display the registration view.
     */
    public function create(): View
    {
        $admin_roles = AdminRole::orderBy('role_name', 'asc')->get();

        return view('auth.admin.register', compact('admin_roles'));
    }

    /**
     * Handle an incoming registration request.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function store(Request $request): RedirectResponse
    {
        //UserをAdminにして、項目を増やす
        $request->validate([
            'name' => ['string', 'max:190'],
            'login_name' => ['required', 'string', 'max:190'],
            'email' => ['required', 'string', 'lowercase', 'email', 'max:190', 'unique:'.Admin::class],
            'password' => ['required', 'confirmed', Rules\Password::defaults()],
        ]);

	//UserをAdminにして、項目を増やす
        $user = Admin::create([
            'name' => $request->name,
            'login_name' => $request->login_name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        event(new Registered($user));

        Auth::login($user);

        return redirect(RouteServiceProvider::ADMIN_HOME);
    }
}

ダッシュボード表示等に使用するAdminControllerをターミナルで作成します。

php artisan make:controller Backend/AdminController

AdminControllerでindexファンクションを記載します。

app\Http\Controllers\Backend\AdminController.php

namespace App\Http\Controllers\Backend;

use App\Http\Controllers\Controller;
use App\Models\Admin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AdminController extends Controller
{
    public function index() {
        $admin = Admin::find(Auth::guard('admins')->id());

        return view('backend.dashboard', compact('admin'));
    }
}

このままroutes\admin.phpのdashboardのルートも修正します。

routes\admin.php
use App\Http\Controllers\Backend\AdminController;//追加

Route::middleware('auth:admins')->group(function () {
  Route::controller(AdminController::class)->group(function () {
    Route::get('/dashboard', 'index')->middleware('verified')->name('dashboard');
  });
});

Admin専用のViewファイル作成

resourcesフォルダのviewsの中に、WEB表示用のBladeファイルが入っています。

AdminのViewのための共通レイアウトファイルを作成

viewsのlayoutsフォルダの中には、コンテンツを表示するための共通ファイルが入っています。
ログイン時にはapp.blade、未ログインにはguest.blade、そしてヘッダー用のメニューが記載されているnavigation.bladeです。
admin専用に、appとnavigationをコピーして、adminとadmin-navigationにリネームします。

layouts\admin.blade.php
@include('layouts.admin-navigation') //ここだけ直す

admin-navigationは全てのrouteにadmin.を追加します。profileのrouteは使わないので消してOK。

layouts\admin-navigation.blade.php
<nav x-data="{ open: false }" class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700">
    <!-- Primary Navigation Menu -->
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex justify-between h-16">
            <div class="flex">
                <!-- Logo -->
                <div class="shrink-0 flex items-center">
                    <a href="{{ route('admin.dashboard') }}">
                        <x-application-logo class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" />
                    </a>
                </div>

                <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
                    <x-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
                        {{ __('Dashboard') }}
                    </x-nav-link>
                </div>
            </div>

            <!-- Settings Dropdown -->
            <div class="hidden sm:flex sm:items-center sm:ms-6">
                <x-dropdown align="right" width="48">
                    <x-slot name="trigger">
                        <button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150">
                            <div>{{ Auth::user()->name }}</div>

                            <div class="ms-1">
                                <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                                    <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                </svg>
                            </div>
                        </button>
                    </x-slot>

                    <x-slot name="content">
                        <x-dropdown-link :href="">
                            {{ __('Profile') }}
                        </x-dropdown-link>

                        <!-- Authentication -->
                        <form method="POST" action="{{ route('admin.logout') }}">
                            @csrf

                            <x-dropdown-link :href="route('admin.logout')"
                                    onclick="event.preventDefault();
                                                this.closest('form').submit();">
                                {{ __('Log Out') }}
                            </x-dropdown-link>
                        </form>
                    </x-slot>
                </x-dropdown>
            </div>

            <!-- Hamburger -->
            <div class="-me-2 flex items-center sm:hidden">
                <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out">
                    <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                        <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
        </div>
    </div>

    <!-- Responsive Navigation Menu -->
    <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
        <div class="pt-2 pb-3 space-y-1">
            <x-responsive-nav-link :href="route('admin.dashboard')" :active="request()->routeIs('admin.dashboard')">
                {{ __('Dashboard') }}
            </x-responsive-nav-link>
        </div>

        <!-- Responsive Settings Options -->
        <div class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600">
            <div class="px-4">
                <div class="font-medium text-base text-gray-800 dark:text-gray-200">{{ Auth::user()->name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
            </div>

            <div class="mt-3 space-y-1">
                <x-responsive-nav-link :href="">
                    {{ __('Profile') }}
                </x-responsive-nav-link>

                <!-- Authentication -->
                <form method="POST" action="{{ route('admin.logout') }}">
                    @csrf

                    <x-responsive-nav-link :href="route('admin.logout')"
                            onclick="event.preventDefault();
                                        this.closest('form').submit();">
                        {{ __('Log Out') }}
                    </x-responsive-nav-link>
                </form>
            </div>
        </div>
    </div>
</nav>

次に、appフォルダの中にViewフォルダがあります。その中のComponentsにAppLayout.phpとGuestLayout.phpがあるので、AppLayout.phpをコピーしてAdminLayout.phpにリネームし,
中を以下のように修正します。

app\View\Components\AdminLayout.php
namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class AdminLayout extends Component
{
    /**
     * Get the view / contents that represents the component.
     */
    public function render(): View
    {
        return view('layouts.admin');
    }
}

Auth認証のAdmin専用ファイルを作成

Auth認証で使用するファイルはauthフォルダの中にあります。
このauthフォルダの中にadminフォルダを作成し、authの中のファイルをすべてコピーします。
auth\admin\の中のファイルを全てAdmin用に修正します。
routeは、先頭にadmin.を付けます。登録に必要な項目も追加を忘れずにします。
以下は登録時のファイルの一例です。

views\auth\admin\register.blade.php
<x-guest-layout>
    <form method="POST" action="{{ route('admin.register') }}">
        @csrf

        <!-- Name -->
        <div class="mt-4">
            <x-input-label for="name" :value="__('Name')" />
            <x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
            <x-input-error :messages="$errors->get('name')" class="mt-2" />
        </div>

        <!-- Email Address -->
        <div class="mt-4">
            <x-input-label for="email" :value="__('Email')" />
            <x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
            <x-input-error :messages="$errors->get('email')" class="mt-2" />
        </div>

        <!-- Login Name -->
        <div class="mt-4">
            <x-input-label for="login_name" :value="__('Login Name')" />
            <x-text-input id="login_name" class="block mt-1 w-full" type="text" name="login_name" :value="old('login_name')" required autofocus
                autocomplete="login_name" />
            <x-input-error :messages="$errors->get('login_name')" class="mt-2" />
        </div>

        <!-- Password -->
        <div class="mt-4">
            <x-input-label for="password" :value="__('Password')" />

            <x-text-input id="password" class="block mt-1 w-full"
                            type="password"
                            name="password"
                            required autocomplete="new-password" />

            <x-input-error :messages="$errors->get('password')" class="mt-2" />
        </div>

        <!-- Confirm Password -->
        <div class="mt-4">
            <x-input-label for="password_confirmation" :value="__('Confirm Password')" />

            <x-text-input id="password_confirmation" class="block mt-1 w-full"
                            type="password"
                            name="password_confirmation" required autocomplete="new-password" />

            <x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
        </div>

        <div class="flex items-center justify-end mt-4">
            <a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" href="{{ route('login') }}">
                {{ __('Already registered?') }}
            </a>

            <x-primary-button class="ms-4">
                {{ __('Register') }}
            </x-primary-button>
        </div>
    </form>
</x-guest-layout>

ログインが成功した時のダッシュボードView作成

先ほど、admin用のログイン時レイアウトファイルを作成しました。
これをダッシュボード表示用のviewに適用します。
viewsフォルダの中に、backendフォルダを作成し、viewsにあるdashboard.blade.phpとwelcome.blade.phpをbackendの中にコピーします。
dashboard.bladeはレイアウトファイル名をx-adminに変更します。

views\backend\dashboard.blade.php
<x-admin-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900 dark:text-gray-100">
                    {{ __("You're logged in!") }}
                </div>
            </div>
        </div>
    </div>
</x-admin-layout>

welcome.bladeはリンク先を変更します。

views\backend\welcome.blade.php
@if (Route::has('admin.login'))
                <div class="sm:fixed sm:top-0 sm:right-0 p-6 text-right z-10">
                    @auth
                        <a href="{{ url('/admin/dashboard') }}" class="font-semibold text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white focus:outline focus:outline-2 focus:rounded-sm focus:outline-red-500">Dashboard</a>
                    @else
                        <a href="{{ route('admin.login') }}" class="font-semibold text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white focus:outline focus:outline-2 focus:rounded-sm focus:outline-red-500">Log in</a>

                        @if (Route::has('admin.register'))
                            <a href="{{ route('admin.register') }}" class="ml-4 font-semibold text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white focus:outline focus:outline-2 focus:rounded-sm focus:outline-red-500">Register</a>
                        @endif
                    @endauth
                </div>
            @endif

paginationの設定

データベースの件数によっては、ページを跨いでの表示になることもあります。
その際にpaginationを利用するのですが、あらかじめデザインパターンがLaravelに用意してあるので、それをターミナルからインストールしておきます。

php artisan vendor:publish --tag=laravel-pagination

Viewの表示でデフォルトに使用するデザインを指定できます。
appフォルダのProviderにある、AppServicePrividerで設定します。

app\Providers\AppServiceProvider.php
use Illuminate\Pagination\Paginator;

    public function boot(): void
    {
        Paginator::defaultView('default');
        Paginator::defaultSimpleView('simple-default');
    }

詳細はドキュメントにあります。

https://laravel.com/docs/10.x/pagination#customizing-the-pagination-view
Customizing The Pagination View

Intervention Image のインストール

コンテンツ制作に画像のアップロードは欠かせません。
そのままアップロードであるなら必要無いのですが、大抵の場合、一定のフォーマットを必要とすると思います。Intervention Imageをインストールすることで簡単に希望通りの画像のアップロードができるようになるのでおすすめです。現在V3です。
呼び出し方と書き方が変わったので注意。

composer require intervention/image
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Imagick\Driver;

// create new manager instance with desired driver
$manager = new ImageManager(new Driver());

https://image.intervention.io/v3/basics/instantiation
Instantiation

Discussion