💬

Laravel Starter Kitを使うにあたって Fortify(認証パッケージ)の設定で抑えておいた方がいい所

に公開

Laravel12 starter Kitを使うなら、まずFortifyのconfigに一度目を通す事

というわけで和訳を置いておく

config/fortify.php
<?php

use Laravel\Fortify\Features;

return [

    /*
    |--------------------------------------------------------------------------
    | Fortify Guard
    |--------------------------------------------------------------------------
    |
    | Fortify がユーザー認証時に使用する Guard を指定します。
    | この値は "auth" 設定ファイルに定義されているガードのいずれかと
    | 対応している必要があります。
    |
    */

    'guard' => 'web',

    /*
    |--------------------------------------------------------------------------
    | Fortify Password Broker
    |--------------------------------------------------------------------------
    |
    | パスワードリセット時に Fortify が使用するパスワードブローカーを
    | 指定します。これは "auth" 設定ファイルで設定されている
    | パスワードブローカーのいずれかに一致している必要があります。
    |
    */

    'passwords' => 'users',

    /*
    |--------------------------------------------------------------------------
    | Username / Email
    |--------------------------------------------------------------------------
    |
    | アプリケーションで「ユーザー名」とみなすモデル属性を指定します。
    | 通常は email を使いますが、変更しても構いません。
    |
    | Fortify はデフォルトで、パスワードリセット関連リクエストに
    | 'email' というフィールド名が送られてくることを期待します。
    | 別の名前にしたい場合はここで定義します。
    |
    */

    'username' => 'email',

    'email' => 'email',

    /*
    |--------------------------------------------------------------------------
    | Lowercase Usernames
    |--------------------------------------------------------------------------
    |
    | ユーザー名をデータベースに保存する前に小文字化するかどうかを指定します。
    | 一部のデータベースは大文字小文字を区別するため、必要に応じて無効化できます。
    |
    */

    'lowercase_usernames' => true,

    /*
    |--------------------------------------------------------------------------
    | Home Path
    |--------------------------------------------------------------------------
    |
    | 認証やパスワードリセット後に、ユーザーをリダイレクトするパスを
    | 指定します。アプリケーションの構成に応じて自由に変更できます。
    |
    */

    'home' => '/dashboard',

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Prefix / Subdomain
    |--------------------------------------------------------------------------
    |
    | Fortify が登録するすべてのルートに付与されるプレフィックスを
    | 指定します。また、必要に応じてサブドメインも変更できます。
    |
    */

    'prefix' => '',

    'domain' => null,

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Middleware
    |--------------------------------------------------------------------------
    |
    | Fortify のルートに適用されるミドルウェアを指定します。
    | 必要であれば変更できますが、通常はこのデフォルトのままで問題ありません。
    |
    */

    'middleware' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | Rate Limiting
    |--------------------------------------------------------------------------
    |
    | デフォルトでは、Fortify はメールアドレスと IP の組み合わせごとに、
    | ログイン試行を 1 分間に 5 回までに制限します。
    | 独自のレートリミッターを使用したい場合はここで指定します。
    |
    */

    'limiters' => [
        'login' => 'login',
        'two-factor' => 'two-factor',
    ],

    /*
    |--------------------------------------------------------------------------
    | Register View Routes
    |--------------------------------------------------------------------------
    |
    | ビューを返すルートを無効にできます。
    | SPA を作っている場合などは不要になるため、その際は false にします。
    |
    */

    'views' => true,

    /*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Fortify のオプション機能です。不要な機能はこの配列から削除すれば
    | 無効化できます。全部消すことも可能です。
    |
    */

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm' => true,
            'confirmPassword' => true,
            // 'window' => 0
        ]),
    ],

];

以下カスタムを行う場合の設定等々を記す

認証機能を一部無効化したい

以下を弄る

    /*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Fortify のオプション機能です。不要な機能はこの配列から削除すれば
    | 無効化できます。全部消すことも可能です。
    |
    */

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm' => true,
            'confirmPassword' => true,
            // 'window' => 0
        ]),
    ],

新規ユーザー登録の無効化

案件により殆どの場合で無効化したいという事も多い機能では?


新規ユーザー登録画面

Features::registration(),を以下のようにコメントアウトすればok

    'features' => [
        // Features::registration(), // <----------- これ!
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm' => true,
            'confirmPassword' => true,
            // 'window' => 0
        ]),
    ],


無効化すると404になる

パスワードリセット機能の無効化


パスワードリセット機能

これはメール送信機能を有効にしていないと動作しないため、メール送信環境を用意できない場合などは無効にしたくなるだろう。

Features::resetPasswords()を無効にする

    'features' => [
        Features::registration(),
        // Features::resetPasswords(), // <----------- これ!
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm' => true,
            'confirmPassword' => true,
            // 'window' => 0
        ]),
    ],

その他の機能

  • Features::emailVerification // メールアドレスの確認機能
  • Features::twoFactorAuthentication // 2FAの機能

なのだが、特にtwoFactorAuthenticationに関してはクセが強く

  • confirm
  • confirmPassword

というような副次的な設定が生えている。これらは、設定時に認証コードの確認を行うかどうかと、設定画面に入る前にパスワード確認ミドルウェアを要求するかどうかの設定である。

なお、2FAの機能を全部無効化しても設定が残る


無効にしてもメニューが残る


押すと403

これも設定状況によって無効化したいところであるが、これらはFortify専用ビューではないため、いわゆる can◯◯ 系の変数はHandleInertiaRequestsで渡す必要があるため、そこそこ加工が必要となる。

app/Http/Middleware/HandleInertiaRequests.php
@@ -5,6 +5,7 @@
 use Illuminate\Foundation\Inspiring;
 use Illuminate\Http\Request;
 use Inertia\Middleware;
+use Laravel\Fortify\Features;

 class HandleInertiaRequests extends Middleware
 {
@@ -46,6 +47,7 @@ public function share(Request $request): array
                 'user' => $request->user(),
             ],
             'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
+            'canManageTwoFactorAuthentication' => Features::canManageTwoFactorAuthentication(),
         ];
     }
 }

ここで渡ってきた値に基づいて resources/js/layouts/settings/layout.tsx を変更する...のだが割愛。

こういうのは面倒なのでオフィシャルで何とかして欲しいところではあるが...今はそうなってないので無効化する場合はメニューが残るのを気にする必要がある。

認証キーをusernameにする場合

これがなかなか面倒で、認証の仕組みを変更する事自体は簡単である。

バックエンドの準備

usernameを認証キーにするなら、usersテーブルにusernameカラムを追加する必要がある。

database/migrations/0001_01_01_000000_create_users_table.php
@@ -13,6 +13,7 @@ public function up(): void
     {
         Schema::create('users', function (Blueprint $table) {
             $table->id();
+            $table->string('username')->unique();
             $table->string('name');
             $table->string('email')->unique();
             $table->timestamp('email_verified_at')->nullable();

seedも

database/seeders/DatabaseSeeder.php
@@ -18,6 +18,7 @@ public function run(): void
         User::firstOrCreate(
             ['email' => 'test@example.com'],
             [
+                'username' => 'test',
                 'name' => 'Test User',
                 'password' => 'password',
                 'email_verified_at' => now(),

このように準備する事で

  • username: test
  • password: password

でログインできるように変更する。

config/fortify.phpの変更

config/fortify.php
@@ -45,7 +45,7 @@
     |
     */

-    'username' => 'email',
+    'username' => 'username',

     'email' => 'email',

このusernameキーによりどのカラムで認証するかを決定している。ここではemailからusernameへと変更している。認証自体はこの変更により一発で通る。

ログインフォームの変更

しかし、viewにおいてキーがemailだといろいろまずい。そもそもhtmlの制約により、email以外受けつけないようになっているので、これをinput type="text"に変更し、関連する名前をusernameに全体的に変更する。文言の整合性も一応合わせているがそれは機能の根本とは関係がないが一応。

resources/js/pages/auth/login.tsx
@@ -25,7 +25,7 @@ export default function Login({
     return (
         <AuthLayout
             title="Log in to your account"
-            description="Enter your email and password below to log in"
+            description="Enter your username and password below to log in"
         >
             <Head title="Log in" />

@@ -38,18 +38,18 @@ export default function Login({
                     <>
                         <div className="grid gap-6">
                             <div className="grid gap-2">
-                                <Label htmlFor="email">Email address</Label>
+                                <Label htmlFor="username">Username</Label>
                                 <Input
-                                    id="email"
-                                    type="email"
-                                    name="email"
+                                    id="username"
+                                    type="text"
+                                    name="username"
                                     required
                                     autoFocus
                                     tabIndex={1}
-                                    autoComplete="email"
-                                    placeholder="email@example.com"
+                                    autoComplete="username"
+                                    placeholder="your.username"
                                 />
-                                <InputError message={errors.email} />
+                                <InputError message={errors.username} />
                             </div>

                             <div className="grid gap-2">

この中で特に

                                 <Input
-                                    id="email"
-                                    type="email"
-                                    name="email"
+                                    id="username"
+                                    type="text"
+                                    name="username"

この箇所においては、先述したtypeの変更に加え、name値を確実にusernameにしている。これはInertiaのFormでは非常に重要な値となる。

これで基本的にログインは可能

ユーザーの新規登録、設定の編集、あるいはテストなどで以下のファイルの変更も必要になる可能性は高い

  • resources/js/pages/auth/register.tsx
  • resources/js/pages/settings/profile.tsx
  • app/Actions/Fortify/CreateNewUser.php
  • app/Models/User.php
  • tests/Feature/Auth/AuthenticationTest.php
  • tests/Feature/Auth/RegistrationTest.php
  • tests/Feature/Auth/TwoFactorChallengeTest.php
  • tests/Feature/Settings/ProfileUpdateTest.php

usernameをプロパティに含める場合は、以下の型定義の更新も必要になるだろう

  • resources/js/types/index.d.ts
resources/js/types/index.d.ts
@@ -32,6 +32,7 @@ export interface SharedData {

 export interface User {
     id: number;
+    username: string;
     name: string;
     email: string;
     avatar?: string;

認証キーをトリミングするなどのカスタムを行いたい

これは以下のようにカスタムユーザー認証の機能を使ってapp/Providers/FortifyServiceProvider.phpに手を入れる

app/Providers/FortifyServiceProvider.php
@@ -4,8 +4,10 @@

 use App\Actions\Fortify\CreateNewUser;
 use App\Actions\Fortify\ResetUserPassword;
+use App\Models\User;
 use Illuminate\Cache\RateLimiting\Limit;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Hash;
 use Illuminate\Support\Facades\RateLimiter;
 use Illuminate\Support\ServiceProvider;
 use Illuminate\Support\Str;
@@ -40,6 +42,24 @@ private function configureActions(): void
     {
         Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
         Fortify::createUsersUsing(CreateNewUser::class);
+
+        Fortify::authenticateUsing(function (Request $request) {
+            $username = $this->normalizeUsername($request->input(Fortify::username()));
+
+            if ($username === null) {
+                return null;
+            }
+
+            $request->merge([Fortify::username() => $username]);
+
+            $user = User::where(Fortify::username(), $username)->first();
+
+            if ($user && Hash::check($request->input('password'), $user->password)) {
+                return $user;
+            }
+
+            return null;
+        });
     }

     /**
@@ -83,9 +103,22 @@ private function configureRateLimiting(): void
         });

         RateLimiter::for('login', function (Request $request) {
-            $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
+            $username = $this->normalizeUsername($request->input(Fortify::username()));
+
+            $throttleKey = Str::transliterate(($username ?? 'guest').'|'.$request->ip());

             return Limit::perMinute(5)->by($throttleKey);
         });
     }
+
+    private function normalizeUsername(?string $username): ?string
+    {
+        if ($username === null) {
+            return null;
+        }
+
+        $normalized = Str::lower(trim($username));
+
+        return $normalized === '' ? null : $normalized;
+    }
 }

このように configureActions(){}内でFortify::authenticateUsingを定義する事でカスタム認証が可能となっている。ここではnormalizeUsername()というプライベートメソッドを作り、trim()およびStr::lower()を適用している。

Discussion