Laravel8 Fortifyで認証機能の動作確認
Laravel8から認証パッケージがLaravel/uiからJetStreamに変わった上JetStreamではLivewire, Inertiaとこれまでに使用したことがない機能の選択まで迫られ認証はどのように行えばいいのだろうかと不安を持った方も多かったかと思います。しかし現在はLaravel Breezeという選択肢も増えたので不安はかなり解消されたとは思います。またLaravel/uiもLaravel8に対応したバージョンは存在します。Laravel8からJetStreamが登場したこともあり今後はJetStreamを使う機会が増えることが予想されます。JetStreamの認証機能の理解を深めるためにはその中のコア機能の一つFortifyの理解が欠かせないのではないかと思いFortifyの動作確認を本文書を公開しました。
本文書を読み終えるとFortify単独のパッケージを利用して認証機能を実装することが可能です。しかし、FortifyにはLaravel7までのlaravel/uiパッケージのようにscaffoldingが用意されているわけではないのでログイン、ユーザ登録画面を自分で作成する必要があります。
Fortifyはユーザの認証だけではなくパスワードリセット、メール確認、パスワードの更新、2要素認証などの機能を持っていますが本文書ではユーザ認証のみ動作確認を行っています。
Laravel Fortifyの動作確認はgithubのドキュメントを参考に行っています。またFortifyの動作確認を行うことを目的としているため途中で作成するログイン画面やユーザ登録画面などではCSSを使った装飾などは行っていません。
目次
Laravel8のインストール
Laravel8をインストールが行える環境が準備されているという前提で話を進めさせて頂きます。
プロジェクト名をlaravel_forityとしてlaravel newコマンドで動作確認用のプロジェクトを作成します。
% laravel new laravel_fortify
fortifyパッケージのインストール
次にfortifyパッケージのインストールをcomposerコマンドで行います。
% cd laravel_fortify
% 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からアクセスを行いたい場合は、TablePlusなどを利用してください。
利用する機能の確認
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を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');
});
}
php artisan serveコマンドでLaravelの開発サーバを起動してください。
この3行を追加しない場合に/loginにブラウザでアクセスすると以下の画面が表示されます。
追加後に再度/loginにアクセスするとエラーメッセージが変わります。今度auth.loginファイルが見つからないというエラーです。
エラーメッセージに表示されているauth.loginファイルはどこにも存在しません。このことからもLaravel Fortifyで認証を利用しようとすると自分でviewファイルを作成しなければならないことがわかります。
resources¥viewディレクトリの下にauthディレクトリを作成し、login.blade.phpファイルを作成します。任意の文字列をいれてください。
再度/loginにアクセスしてその文字列が表示されることを確認してください。ここではlogin.blade.phpファイルにtestと文字列のみ入力しています。
【付録】エラーの原因
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メソッドが実行されることがわかります。
public function create(Request $request): LoginViewResponse
{
return app(LoginViewResponse::class);
}
createメソッドでは、app(‘LoginViewResponse’)が実行されます。app関数が実行されているということはappの引数に対応するサービスがLaravelに登録されているはずなのでそのサービスを見つけ出す必要があります。そのサービスはAuthenticatedSessionController.phpファイルの中には記述されていません。
サービスを見つけたい場合はインストールしたパッケージのサービスプロバイダーファイルを確認する必要があります。今回のパッケージであればFortifyServiceProvider.phpファイルです。
FortifyServiceProvider.phpファイルにFortify::loginViewを追加したことによりエラーが解消したのでLoginViewResponseがどのような処理を行うかは追加したFortify::loginViewの処理の中にあることが予想されます。
Fortify::loginViewはサービスプロバイダーファイルFortifyServiceProvider.phpのbootメソッドの中に登録されているのでアプリケーションにアクセスが来るとFortify::loginViewが実行されます。
//略
Fortify::loginView(function () {
return view('auth.login');
});
//略
Fortify::loginViewのメソッドの中身は、laravel/fortify/src/Fortify.phpファイルで確認でき、Fortify.phpファイルのloginViewメソッドでLoginViewResponseをsingletonで登録しています。
public static function loginView($view)
{
app()->singleton(LoginViewResponse::class, function () use ($view) {
return new SimpleViewResponse($view);
});
}
LoginViewResponseが登録されるのでAuthenticatedSessionController@createでapp(‘LoginViewResponse’)が実行することができます。app(‘LoginViewResponse’)が実行されると登録されたLoginViewResponseに対応するSimpleViewResponseが実行されます。
FortifyServiceProvider.phpにFortify::loadviewを追加していない場合は、LoginViewResponseがLaravelに登録されていないためインスタンス化ができないので下記のエラーが表示されます。
登録している場合は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ファイルで確認、変更することが可能です。
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エラーが表示されるのは正常に動作している証拠です。
なぜ/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にアクセスすると/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にアクセスすると下記の画面が表示されます。ボタンをクリックするとログアウトが行われ”/”にリダイレクトされます。
ログイン画面、ユーザ登録画面についてはCSSで装飾を行う必要がありますが、ユーザ登録、ログイン、ログアウトと認証に必要となる一通りの動作確認を行うことができました。これでJetStreamをインストールすることなくLaravel8に認証機能を追加することが可能です。
シンプルな認証であればLaravel Breezeを利用するとLaravel7のLaravel/uiのようにscaffoldingによりログイン画面、ユーザ登録画面などユーザ認証に必要なファイルが準備されています。