#前提
- Laravel 5.8.31
- make::auth済み
- migrate済み
make:auth
するとweb.php
にAuth::routes()
が追加されます。
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Auth::routes();
はFacadeで、実際には下記のようにaliaseとして定義されています。
laravel/framework/src/Illuminate/Support/Facades/Auth.php
内でRouterクラスのauthメソッドを呼ぶようにしています。
詳しくはこちら
return [
~中略~
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
'Artisan' => Illuminate\Support\Facades\Artisan::class,
'Auth' => Illuminate\Support\Facades\Auth::class,←こいつ
'Blade' => Illuminate\Support\Facades\Blade::class,
~中略~
class Auth extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'auth';
}
/**
* Register the typical authentication routes for an application.
*
* @param array $options
* @return void
*/
public static function routes(array $options = [])
{
static::$app->make('router')->auth($options);
}
}
class Router implements RegistrarContract, BindingRegistrar
{
~中略~
/**
* Register the typical authentication routes for an application.
*
* @param array $options
* @return void
*/
public function auth(array $options = [])
{
// Authentication Routes...
$this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
$this->post('login', 'Auth\LoginController@login');
$this->post('logout', 'Auth\LoginController@logout')->name('logout');
// Registration Routes...
if ($options['register'] ?? true) {
$this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
$this->post('register', 'Auth\RegisterController@register');
}
// Password Reset Routes...
if ($options['reset'] ?? true) {
$this->resetPassword();
}
// Email Verification Routes...
if ($options['verify'] ?? false) {
$this->emailVerification();
}
}
/**
* Register the typical reset password routes for an application.
*
* @return void
*/
public function resetPassword()
{
$this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
}
/**
* Register the typical email verification routes for an application.
*
* @return void
*/
public function emailVerification()
{
$this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
$this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
$this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
}
null合体演算子でoptionの有無でルーティングを決めたりしてますが、パスワードリセットはoptionなしではtrueなのでルーティングに含まれています。
ここから、フォームの表示はForgotPasswordController
のshowLinkRequestForm
で行い
フォームの送信はForgotPasswordController
のsendResetLinkEmail
で行っていることがわかります。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}
これしか記載がありません。
実際はSendsPasswordResetEmails
に色々書かれており、カスタマイズする場合showLinkRequestFormをオーバーライドして色々してきますが、デフォルトだとSendsPasswordResetEmails
に書かれているので見ていきます。
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
trait SendsPasswordResetEmails
{
/**
* Display the form to request a password reset link.
*
* @return \Illuminate\Http\Response
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Validate the email for the given request.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function validateEmail(Request $request)
{
$request->validate(['email' => 'required|email']);
}
/**
* Get the needed authentication credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
return $request->only('email');
}
/**
* Get the response for a successful password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return back()->with('status', trans($response));
}
/**
* Get the response for a failed password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
}
showLinkRequestForm
が定義されており、viewとしてauth/passwords/email.balade.php
返却されている事がわかります。
#メールアドレスを入力し、メール送信
次にフォームにメールアドレスを入力し、postするとForgotPasswordController
のsendResetLinkEmail
が呼ばれるのですが、こちらもデフォルトだとないので、結局SendsPasswordResetEmails
のsendResetLinkEmail
が呼ばれます。
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
$this->validateEmail($request);
で必須チェック、メールアドレス形式かバリデーションをしています。
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
$this->credentials($request);
でリクエストから、email
だけを抜き出しています。
$this->broker()
ではbrokerメソッドでPasswordのFacadeを使ってパスワードリセットに使用するブローカーを返却しています。
vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php
のsendResetLink
が呼ばれます
<?php
namespace Illuminate\Auth\Passwords;
use Closure;
use Illuminate\Support\Arr;
use UnexpectedValueException;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\PasswordBroker as PasswordBrokerContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class PasswordBroker implements PasswordBrokerContract
{
/**
* The password token repository.
*
* @var \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
protected $tokens;
/**
* The user provider implementation.
*
* @var \Illuminate\Contracts\Auth\UserProvider
*/
protected $users;
/**
* The custom password validator callback.
*
* @var \Closure
*/
protected $passwordValidator;
/**
* Create a new password broker instance.
*
* @param \Illuminate\Auth\Passwords\TokenRepositoryInterface $tokens
* @param \Illuminate\Contracts\Auth\UserProvider $users
* @return void
*/
public function __construct(TokenRepositoryInterface $tokens,
UserProvider $users)
{
$this->users = $users;
$this->tokens = $tokens;
}
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
return static::RESET_LINK_SENT;
}
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param \Closure $callback
* @return mixed
*/
public function reset(array $credentials, Closure $callback)
{
// If the responses from the validate method is not a user instance, we will
// assume that it is a redirect and simply return it from this method and
// the user is properly redirected having an error message on the post.
$user = $this->validateReset($credentials);
if (! $user instanceof CanResetPasswordContract) {
return $user;
}
$password = $credentials['password'];
// Once the reset has been validated, we'll call the given callback with the
// new password. This gives the user an opportunity to store the password
// in their persistent storage. Then we'll delete the token and return.
$callback($user, $password);
$this->tokens->delete($user);
return static::PASSWORD_RESET;
}
/**
* Validate a password reset for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword|string
*/
protected function validateReset(array $credentials)
{
if (is_null($user = $this->getUser($credentials))) {
return static::INVALID_USER;
}
if (! $this->validateNewPassword($credentials)) {
return static::INVALID_PASSWORD;
}
if (! $this->tokens->exists($user, $credentials['token'])) {
return static::INVALID_TOKEN;
}
return $user;
}
/**
* Set a custom password validator.
*
* @param \Closure $callback
* @return void
*/
public function validator(Closure $callback)
{
$this->passwordValidator = $callback;
}
/**
* Determine if the passwords match for the request.
*
* @param array $credentials
* @return bool
*/
public function validateNewPassword(array $credentials)
{
if (isset($this->passwordValidator)) {
[$password, $confirm] = [
$credentials['password'],
$credentials['password_confirmation'],
];
return call_user_func(
$this->passwordValidator, $credentials
) && $password === $confirm;
}
return $this->validatePasswordWithDefaults($credentials);
}
/**
* Determine if the passwords are valid for the request.
*
* @param array $credentials
* @return bool
*/
protected function validatePasswordWithDefaults(array $credentials)
{
[$password, $confirm] = [
$credentials['password'],
$credentials['password_confirmation'],
];
return $password === $confirm && mb_strlen($password) >= 8;
}
/**
* Get the user for the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\CanResetPassword|null
*
* @throws \UnexpectedValueException
*/
public function getUser(array $credentials)
{
$credentials = Arr::except($credentials, ['token']);
$user = $this->users->retrieveByCredentials($credentials);
if ($user && ! $user instanceof CanResetPasswordContract) {
throw new UnexpectedValueException('User must implement CanResetPassword interface.');
}
return $user;
}
/**
* Create a new password reset token for the given user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function createToken(CanResetPasswordContract $user)
{
return $this->tokens->create($user);
}
/**
* Delete password reset tokens of the given user.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return void
*/
public function deleteToken(CanResetPasswordContract $user)
{
$this->tokens->delete($user);
}
/**
* Validate the given password reset token.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $token
* @return bool
*/
public function tokenExists(CanResetPasswordContract $user, $token)
{
return $this->tokens->exists($user, $token);
}
/**
* Get the password reset token repository implementation.
*
* @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
*/
public function getRepository()
{
return $this->tokens;
}
}
上記のこの部分ですね。
/**
* Send a password reset link to a user.
*
* @param array $credentials
* @return string
*/
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
// Once we have the reset token, we are ready to send the message out to this
// user with a link to reset their password. We will then redirect back to
// the current URI having nothing set in the session to indicate errors.
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
return static::RESET_LINK_SENT;
}
$user = $this->getUser($credentials);
で$credentials
に与えられたもので、userを取りに行きます。デフォルトだとemailなので、emailでユーザーを取得しています。
ユーザーがいた場合、App\Userインスタンスが返されます。
UserはAuthenticatable(Illuminate\Foundation\Auth\User)
を継承しています
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
~中略~
Authenticatableの中では
Illuminate\Contracts\Auth\CanResetPassword
というinterfaceを/vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php
というtraitをuseすることで実装しています
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
trait CanResetPassword
{
/**
* Get the e-mail address where password reset links are sent.
*
* @return string
*/
public function getEmailForPasswordReset()
{
return $this->email;
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}
つまり、戻ると、この処理では
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
vendor/laravel/framework/src/Illuminate/Auth/Passwords/CanResetPassword.php
のsendPasswordResetNotification
が呼ばれています。
引数として、TokenRepositoryInterface.php
の実装のvendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php
のcreateが呼ばれます。
/**
* Create a new token record.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @return string
*/
public function create(CanResetPasswordContract $user)
{
$email = $user->getEmailForPasswordReset();
$this->deleteExisting($user);
// We will create a new, random token for the user so that we can e-mail them
// a safe link to the password reset form. Then we will insert a record in
// the database so that we can verify the token within the actual reset.
$token = $this->createNewToken();
$this->getTable()->insert($this->getPayload($email, $token));
return $token;
}
getEmailForPasswordReset
でメールアドレスを取得し、$this->deleteExisting($user);
でpassword_reset
テーブルに既に登録されてたら削除し、 $token = $this->createNewToken();
でhash済みのtoken生成後に、再度登録しています。
テーブルに保存したら、tokenを返却しています。
こいつが下記の引数になっています。
$user->sendPasswordResetNotification(
$this->tokens->create($user)
);
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
tokenを渡し、実際のメール送信はNotification
で行っています。
action
はボタンを作ってくれるみたいなので、そこにtokenを連結したものでパスワードリセット画面へのurlを作ってる感じですかね
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this->token);
}
return (new MailMessage)
->subject(Lang::getFromJson('Reset Password Notification'))
->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.'))
->action(Lang::getFromJson('Reset Password'), url(config('app.url').route('password.reset', ['token' => $this->token, 'email' => $notifiable->getEmailForPasswordReset()], false)))
->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')]))
->line(Lang::getFromJson('If you did not request a password reset, no further action is required.'));
}
あとはResetPasswordController@showResetForm
に続くって感じでしょう
FWってすごい(小並感)
お疲れさまでした
今回基本ググらず、追っていたので
ミスや訂正がありましたら、ご指摘いただけると幸いです。
書き終わってから調べたら、わかりやすい記事がありました...
いい勉強になりました...