Laravelの認証機構(Breeze)の設定
以前、このページでLaravelの認証機構のカスタマイズの方法を紹介していましたが、Laravel uiを利用した古いものでしたので、その後継であるLaravel Breezeを使った方法で、リライトします。
ちなみに、Breezeより多機能なLaravel Jetstreamというパッケージもあります。BreezeはJetstreamよりシンプルな構造なので、認証の流れを学ぶには最適ですし、シンプルな分カスタマイズも容易です。
Laravel Breezeのインストール
Laravel:「Starter Kits」のページを参考に、インストールを進めていきます。
BreezeではNode.jsの環境構築が必要ですので、もしNode.jsのJavaScript環境をインストールされていない場合はリンク先を参考にインストールしておいてください。
Laravelプロジェクトを生成します。Breezeの説明の中では見つかりませんでしたが、スターターキットの扱いなのでLaravel uiやJetstream同様に新しいプロジェクトに適用させないと、既存の設定が上書きされてしまうことがあると思います。
プロジェクトのフォルダに入り、インストールします。最終的にLaravelとしてアプリを構成するので、composerでは--devとして開発環境にインストールします。
PS> composer require laravel/breeze --dev
Breezeでは認証用の画面の構築方法を、Blade、React、Vue.js、Next.jsのうちから選択できます。ここではBladeを設定します。Reactを指定する際は、次に紹介するコマンドの後に「react」、Vue.jsなら「vue」、Next.jsなら「api」という文字列の引数を渡します。
インストール後、JavaScript環境のインストールと、コンパイルをします。少し前のバージョンまでは「npm run dev」を実行して、JavaScript環境をコンパイルする必要がありましたが、vite環境のバージョンでは「npm run build」が自動実行されるようです。
PS> php artisan breeze:install # 引数で画面を構築する言語を選択できます # PS> php artisan breeze:install react # PS> php artisan breeze:install vue # PS> php artisan breeze:install api ... PS> npm install ... PS> npm run dev ... ready in 404ms. Laravel v9.19.0 > APP_URL: http://breeze.test
インストール後、はじめはwelcom.blade.phpファイルしかなかった、resources¥viewsフォルダの中に認証用のファイルがいろいろと入っているのが確認できると思います。また、¥routesフォルダ内に認証用のルーティングが記載されたauth.phpが作成され、web.phpにはauth.phpを参照する記述が追加されます。
データベースの設定
ユーザー情報を管理するためのデータベースを設定する必要があります。
MariaDB(MySQL)や他のDBMSの設定に関してはLaravelをデータベースと連携した際の記事を見ていただければと思いますが、一番設定がシンプルで外部アプリも不要な「sqlite」を設定します。
.env
... DB_CONNECTION=sqlite #DB_HOST= #DB_PORT= #DB_DATABASE= #DB_USERNAME= #DB_PASSWORD=
PHPの設定でも、PDO sqlite拡張が必要になります。Windowsでは「extension_dir」 の指定(利用環境に合わせてください)と「extension」のコメントアウトを解除をします。PHPの設定ファイルはLaravelでなくphpのルートフォルダに存在します。
php.ini
... extension_dir = "C:¥php¥ext" ... extension=pdo_sqlite ...
sqliteデータベース用ファイルの作成をします。LaravelのDatabaseフォルダに「database.sqlite」という空のファイルを作ります。この場所と名前は先の.envの設定でDB_DATABASEが存在しない場合のデフォルト値です。もしここに好きな名前をつけたい時は、開発時と稼働時でパスが変わってしまいますので、コード内で対策が必要です。その方法も先に紹介したLaravelとデータベースを連携した際の記事に紹介しています。
データベースの設定が終わったら、「php artisan migrate」を実行します。これで認証に必要なテーブルのすべてが生成されます。エラーなどで止まって後にmigrateの処理がうまく動かなくなった場合は、一旦データベースを全削除する「php artisan migrate:fresh」コマンドを使います。
PS> php artisan migrate ...
もしSQLiteデータベースの中身を確認したいようなことがあるならDB Browser for SQLiteなどを利用して確認します。
テスト稼働
ここまで設定するとテスト稼働が可能になります。先ほどプロンプトを戻すために「npm run dev」の止める必要のあった環境の場合は再度実行し、「php artisan serve」を実行します。デバッグルートにアクセスすると、右上に、「login」と「register」というリンクがあると思います。まずは「register」にアクセスし、ユーザー名やパスワード等を設定します。
パスワードを設定したユーザーは、その認証情報を使ってloginページからアクセスできるようになります。Breezeが設定した初期状態のルーティングは次のようになっています。アクセスはmiddlewareの「auth」に通され、認証されていない時は、/dashbordへはアクセスできないようになっています。
web.php
...
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
require __DIR__.'/auth.php';
パスワードを忘れた際のリセット
一度パスワードを登録したユーザーが、それを忘れた際にリセットする機能がデフォルトで備わっています。
このしくみは、リセット用のアドレスを登録時のメールアドレスに送付するものですが、そのメールはLaravelのメール設定を利用して送信されますので、そちらの設定を.envファイルに記述します。
.env
... MAIL_MAILER=smtp MAIL_HOST="mailsv.your.mail-sv.jp" # メールサーバーのドメイン MAIL_PORT=587 # メールサーバーのポート MAIL_USERNAME="account" # メールサーバーのアカウント MAIL_PASSWORD="password" # メールサーバーのパスワード MAIL_ENCRYPTION="tls" # 暗号化方式 MAIL_FROM_ADDRESS="sender@your.mail-sv.jp" # メール送信者のアドレス MAIL_FROM_NAME="${APP_NAME}" # 送信者名 ...
メール送信時のメッセージは「ResetPassword.php」に記載されています。このファイルは少し深い場所にあります。「vendor¥laravel¥framework¥src¥Illuminate¥Auth¥Notifications¥ResetPassword.php」
ResetPassword.php
...
protected function buildMailMessage($url){
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further action is required.'));
}...
メッセージは、Langファサードの翻訳用のメソッドgetに渡されています。
Breezeではデフォルトでメッセージの日本語化に対応しています。app.phpでロケールをja(日本語)にして、翻訳対象となっているキーワードに日本語訳設定すれば日本語が表示されます。これらはバリデーションエラーと同等の扱いなのでLaravelのバリデーションをカスタマイズした際にエラーメッセージを翻訳した際と同じ方法により日本語化できます。
ロケールが「ja」だった場合、元の英文メッセージがlang¥ja.jsonのオブジェクト内のキーに存在した場合、その値を返すという仕組みなっています。
ちなみに、日本語環境しか考えていないなら、Langファサードのメソッドを取り払って直接日本語のメッセージを書くことも可能です。
カスタマイズ
Breezeから提供された状態でそのまま使うなら以上で設定は終わりですが、さらにカスタマイズしていきたいと思います。
Socialite
次に、Googleアカウント認証を加えたいと思います。 自身で実装することもできますが、Laravel Socialiteを使うと非常に楽です。
PS > composer require laravel/socialite ...
OAuth2.0サービスの認証情報を設定します。これはconfig¥service.phpに記述します。
SocialiteではGoogleの他、facebook、twitter、linkedin、github、gitlab、bitbucketが利用できるようです(twitterでOAuth2を使うにはキーを「twitter-oauth-2」とします)。
config¥service.php
...
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_CALLBACK_URI')
],
...
envとして環境変数を指定していますが、これらのキーはデフォルトでは存在しませんので、自分で書き込みます。
client_idと、client_secretはGoogleOAuth2.0サービスから提供されたものを書き写します。ridirectの部分は、Googleから戻ってくる際のURIを設定し、Google側へ登録します。そのURLは「https://myapp.com/callback-page」といったように絶対表記で記述します。
サーバー側の設定(Googleアカウントを使ったOAuth2.0認証)の方法は、リンク先の記事を参考にしてください。
リンク先では実装のためにPHPのコードを書いてましたが、Socialiteがあればそれらのコードは不要です。ルーターに次のように記述するだけで、ユーザーの認証ができます。
Googleから戻ってきた後の処理はコントローラーに任せるべきものだと思いますが、分かりやすいようにルーターに処理のコードを書いています。
web.php
use App¥Models¥User;
use Illuminate¥Support¥Facades¥Auth;
use Illuminate¥Support¥Facades¥Route;
use Laravel¥Socialite¥Facades¥Socialite;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
// Google認証サービスへリダイレクト
Route::get('/ridirect-to-google', function () {
return Socialite::driver('google')->redirect();
});
// Google認証後に戻ってくるページ
Route::get('/callback', function () {
// 戻ってきた後の処理
// 本当はコントローラーで処理する方が丁寧
$googleUser = Socialite::driver('google')->user();
// emailを基準にユーザーの既存を確認して、
// 存在しなかったら作成、存在したら更新
$user = User::updateOrCreate([
'email' => $googleUser->email,
], [
'name' => $googleUser->name,
'email' => $googleUser->email,
// googleから得られる情報 DBテーブルに対象のカラムがないのでコメントアウト
//'google_token' => $googleUser->token,
//'google_refresh_token' => $googleUser->refreshToken,
]);
// ユーザーをログイン済みに
Auth::login($user);
// ログイン後のページへ
return redirect('/dashboard');
});
require __DIR__.'/auth.php';
「/ridirect-to-google」のページを表示させると、Googleサーバーの認証ページへリダイレクトします。このリダイレクト先のアドレスはなにも設定していませんが、Socialiteの中に保持されています。
リダイレクト先で認証がされると、「/callback」ページに戻ってきます。Googleに認証されたらUserモデルを利用してデータベースにユーザー登録の状態を確認して、Authファサードのloginメソッドをユーザーデータを引数に呼ぶことでログイン処理を完了させています。
このあと、ユーザーのページ(/dashboard)へリダイレクトして一連の処理が終了します。
つまり、ログインページにGoogle認証用のボタンを作って、クリック時に「/ridirect-to-google」へリンクさせることでGoogleアカウント認証が可能になります。
Socialite内部の話なので意識する必要はないかもしれませんが、コールバック時にgetパラメータとして認証結果を問い合わせるためのトークン(token)を受け取ります。Socialiteはそのトークンを使ってGoogleに問い合わせを行っています。そのトークンがコード内にあるtokenに入っています。
これらの処理時に次のようなエラーが出るかもしれません。
これはQuiita:「Windows版PHPのcurlの証明書」によれば、cURLでのSSL通信に必要なCA証明書が見つからないことが原因なのだそうです。
ここでは、 composer/ca-bundleで利用される「cacert.pem」を使わせてもらいます。インストールしてもいいですが、githubのページのresフォルダ内にそのままの形で入っていますので、ダウンロードします(cacert.pemのソースを開いて「raw」ボダンを右クリック、名前を付けて保存)。
ダウンロードしたファイルのフルパスを、php.iniに記述します。
php.ini
... [curl] curl.cainfo =C:/php/cacert.pem ...
また、CA証明書の問題を解決すると今度は次のようなエラーが出るとおもいます。
これはUserテーブルのパスワードが必須な状態になっているのが原因です。通常はパスワードが必須なのでそのような設定になっていますがすが、Googleアカウント認証経由の場合は、パスワードはGoogle側のみが管理し、Laravelには保存しません。
それに対応するため、database¥migrations¥2014_10_12_000000_create_users_table.phpを修正します。このファイルはもしかしたら先頭部のプレフィックスが違うかもしれません。
...create_users_table.php
... public function up(){ Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); //$table->string('password'); $table->string('password')->nullable(); $table->rememberToken(); $table->timestamps(); }); }...
そのあと、データベースを初期化します。(データベース内のすべてのデータが削除されますので注意してください)
クライアント証明書認証
最後に、クライアント証明書認証を設定してみます。
これについては、Laravelのミドルウェアについて理解していると、何をしているか分かりやすいと思います。
まずミドルウェアを作ります。
PS > php artisan make:middleware ClientCert
コーディング例としては次のようになります。クライアント認証がされた際に$_SERVER['SSL_CLIENT_S_DN_CN']でCommon Nameが取得できるので、それが存在した場合にはその情報をもとにログインさせます。
ClientCert.php
<?php
namespace App¥Http¥Middleware;
use Closure;
use Illuminate¥Http¥Request;
class GotoDebug{
public function handle(Request $request, Closure $next){
// request()でも可
// クライアント認証のCommon Nameにメールアドレスが入っている前提です
$clientCert = $request->server()->get('SSL_CLIENT_S_DN_CN');
if (isset($clientCert) && $clientCert!=='') {
$user = User::updateOrCreate([
'email' => $clientCert,
], [
// 証明書を発行する際に、common name(メールアドレス)と
// ユーザー名を結び付けておいて、ここで解決してください
'name' => commonNameToRealName($clientCert),
'email' => $clientCert,
]);
// ユーザーをログイン済みに
Auth::login($user);
}
// 指定のページへ
return $next($request);
}
}
App¥Http¥Kernel.phpにミドルウェアを登録します。ここでは全ページ対象にしています。
Kernel.php
...
protected $middleware = [
...
¥App¥Http¥Middleware¥ClientCert::class,
...
メールアドレスの確認
デフォルトではアカウント作成時に指定したメールアドレスのチェックを行わずに、設定したアカウントが有効になります。
アカウント作成時にメールアドレスのチェックを行うようにするには、モデルでMustVerifyEmailをimplementします。そうすると登録時に確認用のメールアドレスを含んだメールを送信するようになります。また、ルーターでログインが必要な箇所にverifiedミドルウェアを設定します。
User.php
class User extends Authenticatable implements MustVerifyEmail {
...
web.php
...
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth','verified'])->name('dashboard');
...
こちらの方法はQuiita:「Laravel:Email Verificationを使う」を参考にさせていただきました。ありがとうございました。