19
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel5.7のMulti Auth環境で正しくパスワードリセットを動作させる

Last updated at Posted at 2018-11-18

この記事の続きとして書きます。
ここでいう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)

主な流れは、

  1. Loginページにて、Forgot Your Password?リンクをクリックすると、GETでpassword/resetが呼ばれshowLinkRequestFormでemail確認Formが表示される。
  2. email確認Formにてメールを入力しSend Password Reset Linkボタンを押すとPOSTでpassword/emailが呼ばれsendResetLinkEmailにて、リセット用のメールが送信される。
  3. リセットメールのURLをクリックするとGETで、/password/reset/{token}が呼ばれ、showResetFormでリセット用のFormが表示される。
  4. リセットFormにてemailと新パワードを入れてReset Passwordボタンを押すとPOSTにてpassword/resetが呼ばれresetが実行される。

ルーティング(抜粋)

prefixにadminを指定しているので、実際は/admin/password/reset等となります。

web.php
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別に作成する必要はありません(実装に依存しますが)。

config/auth.php
'passwords' => [
    'users' => [
        'provider' => 'users',
        'table' => 'password_resets',
        'expire' => 60,
    ],
+    'admins' => [
+        'provider' => 'admins',
+        'table' => 'password_resets',
+        'expire' => 60,
+    ],
],

ForgetPasswordController.php

コントローラーを実装します。基本的にadminsで正しく動作するようにオーバーライドするだけです。

ForgetPasswordController.php
<?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の送信については試せていません。試したら別途記事化します。

参考

19
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?