Laravel8から認証にJetStreamが登場したことで認証はどうすればいいのだろうと悩んでいる人もいるかと思います。私もその一人です。JetStreamの理解を深めるためにはその中のコア機能の一つFortifyの理解が欠かせないのではないかと思いFortifyの動作確認を行いました。

本文書を読み終えるとFortify単独のパッケージを利用して認証機能を実装することが可能です。しかし、Laravel7までのようにscaffoldingが用意されているわけではないのログイン、ユーザ登録画面を自分で作成する必要があります。

Fortifyはユーザの認証だけではなくパスワードリセット、メール確認、パスワードの更新、2要素認証などの機能を持っていますが本文書ではユーザ認証のみ動作確認を行っています。

Laravel Fortifyの動作確認はgithubのドキュメントを参考に行っています。またFortifyの動作確認を行うことを目的としているため途中で作成するログイン画面やユーザ登録画面などではCSSを使った装飾などは行っていません。

Laravel8のインストール

Laravel8をインストールが行える環境が準備されているという前提で話をさせてもらいます。

プロジェクト名をlaravel_forityとしてlaravel newコマンドで動作確認用のプロジェクトを作成します。


 % laravel new laravel_fortify

fortifyパッケージのインストール

次にfortifyパッケージのインストールをcomposerコマンドで行います。


 % composer require laravel/fortify

fortifyに関連するファイルの作成をphp artisan vendor:publishコマンドで行います。


 % php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

コマンドを実行すると実行するとFortifyに関連するテーブルのマイグレーションファイル(2014_10_12_200000_add_two_factor_columns_to_users_table)やapp/Actionsディレクトリにユーザ登録のバリデーションが含まれるCreateNewUser.phpファイルといったファイルが作成されます。

どのようなファイルがFortifyに関連するのかを知る機会なので各ファイルの中身を確認することもおすすめです。実行ログから/vendor/laravel/fortify/以下にfortifyに関するファイルが保存されていることもわかります。


 % php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
Copied File [/vendor/laravel/fortify/stubs/fortify.php] To [/config/fortify.php]
Copied File [/vendor/laravel/fortify/stubs/CreateNewUser.php] To [/app/Actions/Fortify/CreateNewUser.php]
Copied File [/vendor/laravel/fortify/stubs/FortifyServiceProvider.php] To [/app/Providers/FortifyServiceProvider.php]
Copied File [/vendor/laravel/fortify/stubs/PasswordValidationRules.php] To [/app/Actions/Fortify/PasswordValidationRules.php]
Copied File [/vendor/laravel/fortify/stubs/ResetUserPassword.php] To [/app/Actions/Fortify/ResetUserPassword.php]
Copied File [/vendor/laravel/fortify/stubs/UpdateUserProfileInformation.php] To [/app/Actions/Fortify/UpdateUserProfileInformation.php]
Copied File [/vendor/laravel/fortify/stubs/UpdateUserPassword.php] To [/app/Actions/Fortify/UpdateUserPassword.php]
Copied Directory [/vendor/laravel/fortify/database/migrations] To [/database/migrations]
Publishing complete
基本的にはここで作成されているファイルをユーザが自由に更新することが可能です。

Fortifyをインストールすることでどのようなルーティングが追加されたはphp artisan route:listコマンドを実行して確認することができます。Fortifyに関連する非常にたくさんのルーティングが追加されていることが確認できます。

データベースの作成

マイグレーションファイルが追加されたのでデータベースを作成して、テーブルの作成を行います。本文書では簡易的に動作確認を行うためsqliteを利用します。

Laravelのプロジェクトディレクトリにdatabaseディレクトリの下にdatabase.sqliteファイルを作成します。


 % touch database/database.sqlite

LaravelからSqliteデータベースに接続するため.envファイルを書き換えます。DB_と接頭語がついている変数でDB_CONNECTION以外を削除しDB_CONNECTIONにはsqliteを設定します。


DB_CONNECTION=sqlite

これでデータベースの設定は完了したので、php artisan migrateコマンドを実行してください。4つのテーブルが作成されます。


 % php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (2.37ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (2.27ms)
Migrating: 2014_10_12_200000_add_two_factor_columns_to_users_table
Migrated:  2014_10_12_200000_add_two_factor_columns_to_users_table (1.77ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (1.53ms)

テーブルへのGUIからアクセスを行いたい場合は、

利用する機能の確認

Fortifyはapp¥config¥fortify.phpファイルでFortifyに関する設定を変更することができます。どの機能を利用するかもこのファイルで設定可能です。

今回は認証部分のみの動作確認を行うため、その他の機能はコメントアウトします。


'features' => [
    Features::registration(),
    // Features::resetPasswords(),
    // // Features::emailVerification(),
    // Features::updateProfileInformation(),
    // Features::updatePasswords(),
    // Features::twoFactorAuthentication([
    //     'confirmPassword' => true,
    // ]),
],

設定後にphp artisan route:listを実行してみてください。先ほどに比べてルーティングが少なくなっていることが確認できます。

文字が小さくて見えにくいですが、認証に必要なルーティングのlogin, logout, registerを確認することができます。このリストのAction列を確認することで各URLにアクセスした際に実行されるコントローラーとメソッドの情報もわかります。

ルーティングの確認
ルーティングの確認

ServiceProviderの登録

先ほど実行したphp artisan vendor:publishコマンドではapp/Providersディレクトリの下にサービスプロバイダーのFortifyServiceProvider.phpファイルが作成されます。

FortifyServiceProvider.phpをapp¥config¥app.phpファイルに追加します。


//略
        App\Providers\RouteServiceProvider::class,
        App\Providers\FortifyServiceProvider::class, //追加
//略

サービスプロバイダーのapp.phpファイルへの登録が完了したらFortifyServiceProvider.phpにログイン画面を表示するviewファイルの設定を行います。

Fortify::loginViewからの3行が新たに追加する行です。


public function boot()
{
    Fortify::createUsersUsing(CreateNewUser::class);
    Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
    Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
    Fortify::resetUserPasswordsUsing(ResetUserPassword::class);

//以下を追加
    Fortify::loginView(function () {
        return view('auth.login');
    });
}
Fortify::createUsersUsing以外の行は今回の動作確認では利用しない機能なのでコメントアウトすることも可能です。

この3行を追加しない場合に/loginにブラウザでアクセスすると以下の画面が表示されます。

/loginアクセス時のエラー
/loginアクセス時のエラー

追加後に再度/loginにアクセスするとエラーメッセージが変わります。今度auth.loginファイルが見つからないというエラーです。

viewファイルに関するエラー
viewファイルに関するエラー

エラーメッセージに表示されているauth.loginファイルはどこにも存在しません。Laravel Fortifyで認証を利用しようとすると自分でviewファイルを作成しなければならないことがわかります。

resources¥viewディレクトリの下にauthディレクトリを作成し、login.blade.phpファイルを作成します。任意の文字列をいれてください。

再度/loginにアクセスしてその文字列が表示されることを確認してください。

画面に文字列が表示されることを確認
画面に文字列が表示されることを確認

【付録】エラーの原因

FortifyServiceProvider.phpにviewの情報を追加したらなぜエラーが変わるのかを見ていきましょう。コードの流れを追うだけなのでスキップしても問題ありません。

URLの/loginにブラウザでアクセスするとどの処理が行われるかはphp artisan route:listを実行してloginに対応するコントローラーとメソッドを確認することでわかります。

php artisan route:listを実行し、そのURI列でloginを探します。loginのAction列を見るとLaravel\Fortify\Http\Controllers\AuthenticatedSessionController@createを確認することができます。この結果、/loginにアクセスするとAuthenticatedSessionControllerコントローラーのcreateメソッドが実行されることがわかります。

createメソッドでは、app(‘LoginViewResponse’)が実行されます。app関数が実行されているということはappの引数に対応するサービスがLaravelの中で登録されているそれを見つける必要があります。そのサービスはAuthenticatedSessionController.phpファイルの中には記述されていません。

その場合は基本はインストールしたパッケージのサービスプロバイダーファイルを確認する必要があります。今回のパッケージであればFortifyServiceProvider.phpファイルです。Laravelのサービスプロバイダーを理解しておかなれればサービスプロバイダーファイルが関係しているのか理解するのは難しいかもしれません。

LoginViewResponseがどのような処理を行うかは追加したFortify::loadviewの処理で確認する必要があります。


public function create(Request $request): LoginViewResponse
{
    return app(LoginViewResponse::class);
}

Fortify::loadviewはサービスプロバイダーファイルFortifyServiceProvider.phpのbootメソッドの中に登録されているのでアプリケーションにアクセスが来るとFortify::loadviewが実行されます。



    Fortify::loginView(function () {
        return view('auth.login');
    });

Fortify::loginViewのメソッドの中身は、laravel/fortify/src/Fortify.phpファイルで確認でき、Fortify.phpファイルのloadviewメソッドでLoginViewResponseをsingletonで登録しています。


public static function loginView($view)
{
    app()->singleton(LoginViewResponse::class, function () use ($view) {
        return new SimpleViewResponse($view);
    });
}

LoginViewResponseが登録されるのでAuthenticatedSessionController@createでapp(‘LoginViewResponse’)が実行すると登録されたLoginViewResponseに対応するSimpleViewResponseが実行されます。

FortifyServiceProvider.phpにFortify::loadviewを追加していない場合は、LoginViewResponseが登録されていないためインスタンス化ができないので下記のエラーが表示されます。

/loginアクセス時のエラー
/loginアクセス時のエラー

登録している場合はSimpleViewResponseがインスタンス化されその引数に設定されているauth.loginファイルが呼ばれることになります。

ログイン画面の作成

Fortifyの公式ドキュメントには/loginに対してemailとpasswordを含んだPOSTリクエストを送信するとFortifyが認証の処理を行ってくれると記載されているので実際にそのような動作になるのか確認してみましょう。

先ほどから説明している通り、Fortifyでは各自でログイン画面を作成する必要があります。下記ではemailとpasswordの入力欄を持つログイン画面を作成しています。バリデーションのエラーが表示されること以外については通常のHTMLの入力フォームと同じだと思いますので各自で簡単に作成することができます。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>

    <h1>ログイン画面</h1>
    @if ($errors->any())
        <div>
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif

    <form  method="POST" action="/login">
        @csrf
        <div>
            <label for="email">メールアドレス</label>
            <input name="email" type="email" value="{{old('email')}}"/>
        </div>
        <div>
            <label for="password">パスワード</label>
            <input name="password" type="password" />
        </div>
        <div>
            <button type="submit">送信</button>
        </div>
    </form>
</body>
</html>

ブラウザで表示するとメールアドレスとパスワードの入力欄を持った入力フォームが表示されます。

ログイン画面作成
ログイン画面作成

実際にメールアドレスとパスワードを入れてみましょう。ユーザの登録を行っていないのでログインは成功することはありません。任意のメールアドレスを入力し送信ボタンを押します。

Fortifyの公式ドキュメント通り、/loginのPOSTリクエストを送信すると認証の処理が行われValidationエラーが表示されることが確認できます。

ログインエラーが表示される
ログインエラーが表示される

このようにFortifyでは自分でログイン画面を作成し、/loginのPOSTリクエストを送ることで認証処理が行われることがわかりました。

つまりバックエンド側の認証処理はFortifyが行ってくれるので、ユーザはフロントエンド側の画面の作成に集中することができます。

ログイン画面の作成が完了したので次はユーザの登録をどのように行うのかとユーザ登録画面の作成を行っていきます。

ユーザ登録の処理

ユーザの登録画面の作成はユーザのログイン画面の作成と同様です。FortifyServiceProviderにviewの追加設定を行い、各自でregister.blade.phpファイルを作成します。

Fortify::registerViewを追加することでブラウザから/registerにアクセスするとauth.registerが存在する場合auth.registerに記述した内容が表示されるようになります。


public function boot()
{
//略
    Fortify::loginView(function () {
        return view('auth.login');
    });
    Fortify::registerView(function () {
        return view('auth.register');
    });
}

authの下にregister.blade.phpファイルを作成します。ログインの時とは異なり、/registerのPOSTリクエストではname, email, password, password_confirmationの4つを追加する必要があります。

ユーザ登録のバリデーションはFortifyをphp artisan vendro:publishを実行した際に作成されるapp¥Actionsの下のCreateNewUser.phpファイルで確認、変更することが可能です。

php artisan vendor:publishコマンドを実行時に作成されるapp¥Actions以下はユーザがカスタマイズを行えるように提供されているファイルです。Fortifyでは各種画面の作成とバリデーションをカスタマイズできることを想定して作成されています。内部の認証のロジックについてはドキュメントに記載がないのでユーザがそれらのファイルの更新することを想定していません。

public function create(array $input)
{
    Validator::make($input, [
        'name' => ['required', 'string', 'max:255'],
        'email' => [
            'required',
            'string',
            'email',
            'max:255',
            Rule::unique(User::class),
        ],
        'password' => $this->passwordRules(),
    ])->validate();

    return User::create([
        'name' => $input['name'],
        'email' => $input['email'],
        'password' => Hash::make($input['password']),
    ]);
}

register.blade.phpファイルを下記のように作成しました。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <h1>ユーザ登録画面</h1>
    @if ($errors->any())
        <div>
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
    <form  method="POST" action="{{ route("register") }}">
        @csrf
        <div>
            <label for="name">ユーザ名</label>
        <input name="name" type="text" value="{{ old('name') }}"/>
        </div>
        <div>
            <label for="email">メールアドレス</label>
        <input name="email" type="email" value="{{ old('email') }}"/>
        </div>
        <div>
            <label for="email">パスワード</label>
            <input name="password" type="password"/>
        </div>
        <div>
            <label for="email">パスワード確認</label>
            <input name="password_confirmation" type="password"/>
        </div>
        <div>
            <button type="submit">登録</button>
        </div>
    </form>    
</body>
</html>

ブラウザで確認するとユーザの登録画面が表示されます。

ユーザ登録画面
ユーザ登録画面

ユーザ登録のバリデーションが正常に行われるか確認するためにユーザ名のみを入力して登録ボタンをクリックします。emailとpasswordを入れていないのでemail, passwordが必須フィールドであることを通知するエラーメッセージが表示されます。

ユーザ登録のバリデーションエラー表示
ユーザ登録のバリデーションエラー表示

次は4つの項目をすべてを埋めます。パスワードとパスワード確認は同じパスワードを入力してください。

ユーザ情報を入力
ユーザ情報を入力

登録ボタンを押すと404 NOT FOUNDエラーが表示されます。正常に動作しないと不安になるかもしれませんが、ブラウザのURLを見てください。

ブラウザのURLを見るとユーザの登録が正常に完了するとhttp://127.0.0.1/homeにリダイレクトされることがわかります。しかし、/homeについてはルーティングの追加を行っていないため存在しません。そのため404 NOT FOUNDエラーが表示されるのは正常に動作している証拠です。

404 NOT FOUNDエラー
404 NOT FOUNDエラー

なぜ/homeにリダイレクトされるかはfortifyの設定ファイルである¥app¥config¥fortify.phpで確認することができます。


    'home' => RouteServiceProvider::HOME,

app¥Providers¥RouteServiceProvider.phpを見ると/homeに設定されていることがわかります。この値を設定することで別の場所にログイン後にリダイレクトすることができます。


public const HOME = '/home';

/homeにリダイレクトされることがわかったのでルーティングをweb.phpファイルに追加します。ログインしたユーザのみアクセスできるようにmiddlewareでauthを設定しています。


Route::get('/home', function () {
    dd('ログイン成功');
})->middleware('auth');

追加後先ほどエラーが発生したブラウザをリロードしてください。画面にログイン成功が表示されることが確認できます。

homeへのアクセス確認
homeへのアクセス確認

ログインした状態になっているので一度ログアウトを行います。

ログアウトの方法はブラウザの機能を利用してCookieをSession情報を削除する方法やコードにAuth::logout()を記述して強制的にログアウトする方法などいろいろあると思うので好きな方法で行ってください。

ログインアウトした後に再度/homeにアクセスすると/loginにリダイレクトされるか確認を行ってください。本文書の手順通りに行って入れればユーザはログアウト状態なのでmiddlewareのauthによって認証に失敗してリダイレクトされユーザのログイン情報を入力する/login画面が表示されます。

ログイン画面が表示されたら登録したユーザでログインを行い、/homeにアクセスできるか確認を行ってください。ログインができ、/home画面にアクセスできれば正常にユーザ登録、ユーザログインの処理が行われていることになります。

ログアウトの処理

ログアウトについては/logoutにアクセスするだけではなくPOSTリクエストを送信する必要があります。

下記のようにHome.blade.phpファイルを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
</head>
<body>
    <h1>ホーム画面</h1>
    <p>ようこそLaravelアプリケーションへ</p>
    <form method="POST" action="{{ route('logout') }}">
        @csrf
        <button type="submit">ログアウト</button>
    </form>
</body>
</html>

ログイン中に/homeにアクセスすると下記の画面が表示されます。ボタンをクリックするとログアウトが行われ”/”にリダイレクトされます。

Home画面にログアウトボタンを設置
Home画面にログアウトボタンを設置

ログイン画面、ユーザ登録画面についてはCSSで装飾を行う必要がありますが、ユーザ登録、ログイン、ログアウトと認証に必要となる一通りの動作確認を行うことができました。これでJetStreamをインストールすることなくLaravel8に認証機能を追加することが可能です。

Email Veridationなどの機会はまた別の機会に動作確認を行い情報を公開したいと思います。