この記事の続きとして書きます。
ここでいうMulti Authとは、userとadminで認証情報を保持するテーブルを分け、個別にlogin, logoutする機能です。
Multi AuthにおけるLogin, Logoutについては過去に沢山記事を書いてきたのですが、パスワードリセットについては「同じようにやれば動くだろう」と後回しにしてきました。が、実際は、
- ルーティングはどのように設定すればよいのか?
- コントローラはどのように記述すればよいのか?
- パスワードリセットのURLとかはどのように切り替えればいいのか?
等、考えなければいけないことはいろいろあります。それを試してみます。
準備
ディレクトリ構造
記述はディレクトリ構造に依存します。
make:authすると、
app/Http/Controllers/Authができますが、これはuser用に利用し、admin用に
app/Http/Controllers/Admin/Authを作成して利用します。admin以下のAuthは基本、make:authでできたAuthをコピーして利用します。
その他view周りもいじる必要がありますが、詳細は前提となるページを見て下さい。
メール送信
パスワードリセットはメール送信を行う必要があります。
今回はmailtrapを利用して確認します。mailtrapでアカウントを取得し、.envにアカウントとパスワードを設定するだけで利用できます。
実装
概要
パスワードリセット機能は、4つのルートを2つのコントローラー(2つのView)で処理することで実現されています。
- ForgotPasswordController.phpはパスワード忘れ画面を表示しメールを送信までを担当します(下記1,2)
- ResetPasswordController.phpはメールに添付されたURLがクリックされたらリセットフォームを表示しリセットをする機能を担当します(下記3,4)
主な流れは、
- Loginページにて、Forgot Your Password?リンクをクリックすると、GETでpassword/resetが呼ばれshowLinkRequestFormでemail確認Formが表示される。
- email確認Formにてメールを入力しSend Password Reset Linkボタンを押すとPOSTでpassword/emailが呼ばれsendResetLinkEmailにて、リセット用のメールが送信される。
- リセットメールのURLをクリックするとGETで、/password/reset/{token}が呼ばれ、showResetFormでリセット用のFormが表示される。
- リセットFormにてemailと新パワードを入れてReset Passwordボタンを押すとPOSTにてpassword/resetが呼ばれresetが実行される。
ルーティング(抜粋)
prefixにadminを指定しているので、実際は/admin/password/reset等となります。
Route::group(['prefix' => 'admin'], function(){
//home
Route::get('home', 'Admin\HomeController@index')->name('admin.home');
//login
Route::get('login', 'Admin\Auth\LoginController@showLoginForm')->name('admin.login');
Route::post('login', 'Admin\Auth\LoginController@login')->name('admin.login');
//logout
Route::post('logout', 'Admin\Auth\LoginController@logout')->name('admin.logout');
//Register
Route::get('register', 'Admin\Auth\RegisterController@showRegisterForm')->name('admin.register');
Route::post('register', 'Admin\Auth\RegisterController@register')->name('admin.register');
//reset
+ Route::post('password/email', 'Admin\Auth\ForgotPasswordController@sendResetLinkEmail')->name('admin.password.email');
+ Route::get('password/reset', 'Admin\Auth\ForgotPasswordController@showLinkRequestForm')->name('admin.password.request');
+ Route::post('password/reset', 'Admin\Auth\ResetPasswordController@reset')->name('admin.password.update');
+ Route::get('password/reset/{token}', 'Admin\Auth\ResetPasswordController@showResetForm')->name('admin.password.reset');
});
Password Reset (broker)の設定
Password Resetはリセット用のtokenを保存しておくテーブルが必要です。
そのテーブルはconefig/auht.php内で設定しています(前の記事)。
なお、リセット用のテーブルはuserと共有できるため、わざわざguard別に作成する必要はありません(実装に依存しますが)。
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
+ 'admins' => [
+ 'provider' => 'admins',
+ 'table' => 'password_resets',
+ 'expire' => 60,
+ ],
],
ForgetPasswordController.php
コントローラーを実装します。基本的にadminsで正しく動作するようにオーバーライドするだけです。
<?php
+namespace App\Http\Controllers\Admin\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
public function __construct()
{
+ $this->middleware('guest:admin');
}
+ public function showLinkRequestForm()
+ {
+ return view('admin.auth.passwords.email');
+ }
+ protected function guard()
+ {
+ return \Auth::guard('admin');
+ }
+ public function broker()
+ {
+ return \Password::broker('admins');
+ }
}
Viewの変更:admin.auth.passwords.email
パスワードリセットするmailアドレスを表示するフォームです。
admin用に作成したテンプレートの読み込みと、フォーム送信先をadmin以下に変更しています。
+@extends('layouts.admin')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
+ <form method="POST" action="{{ route('admin.password.email') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
ResetPasswordController.php
続いてリンククリック後の機能です。
こちらも基本的にはadmin以下で動作するよう各機能をオーバーライドしています。
<?php
+namespace App\Http\Controllers\Admin\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
+use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
+ protected $redirectTo = '/admin/home';
public function __construct()
{
+ $this->middleware('guest:admin');
}
+ public function showResetForm(Request $request, $token = null)
+ {
+ return view('admin.auth.passwords.reset')->with(['token' => $token, 'email' => $request->email]);
+ }
+ protected function guard()
+ {
+ return \Auth::guard('admin');
+ }
+ public function broker()
+ {
+ return \Password::broker('admins');
+ }
}
Viewの変更:admin.auth.passwords.reset
パスワードリセットフォーム表示用です。
admin用に作成したテンプレートの読み込みと、フォーム送信先をadmin以下に変更しています。
+@extends('layouts.admin')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
+ <form method="POST" action="{{ route('admin.password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus>
@if ($errors->has('email'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>
@if ($errors->has('password'))
<span class="invalid-feedback" role="alert">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
リセットURLの変更
ここまでのコードでページ遷移等はうまく動きます。が、メールで送信されるリセット用のURLがhostname/password/reset/xxxxとuser用のままでadmin用として機能してくれません。URLの変更を行います。
送信機能のオーバーライド
メール送信はResetPasswordNotificationのtoMail()で行われているので、その内容をadmin用にオーバーライドします。
app以下にNotificationsというフォルダを作成し、以下を、AdminPasswordResetNotification.phpとして保存します。
<?php
namespace App\Notifications;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
use Illuminate\Notifications\Messages\MailMessage;
class AdminPasswordResetNotification extends ResetPasswordNotification
{
public function toMail($notifiable)
{
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url(config('url').route('admin.password.reset', $this->token, false)))
->line('If you did not request a password reset, no further action is required.');
}
}
モデルの変更
モデルにてAdminPasswordResetNotification.phpでの設定が利用されるよう必要な記述を行います。
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
+use App\Notifications\AdminPasswordResetNotification;
class Admin extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
+ public function sendPasswordResetNotification($token)
+ {
+ $this->notify(new AdminPasswordResetNotification($token));
+ }
}
これでOKです。
動作確認
上記の処理をしてもURLはlocalhost等(ポート無し)で送信されます。php artisan serveを利用している場合、必要に応じてlocalhost:8000等にリンクを変換して動作確認する必要があります。
その他
今回は5.7から追加されたverify mailの送信については試せていません。試したら別途記事化します。