Here's how set your password reset experience so that the user doesn't have to enter their email address... without altering vendor/core - tested with Laravel 5.8 (should be fine with later versions, too):
Firstly create a new notification for your app:
php artisan make:notification ResetPassword
Then open the newly created file: app\Notifications\ResetPassword.php
and make the following changes:
Add public $token;
to the beginning of the class (ie. after use Queueable
).
Add $this->token = $token;
to the body of your __construct()
method.
Add $token
as parameter to the __construct()
method (so it reads __construct($token)
).
Add the following to the body of your toMail()
method (replacing what's there by default):
return (new MailMessage)
->line('You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', url(config('app.url').route('password.reset', [$this->token, $notifiable->email], false)))
->line('If you did not request a password reset, no further action is required.');
What you've done is create a copy of the original vendor/core version of the ResetPassword notification (found in /vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php
). This allows you to make customisations to it without changing the core Laravel files.
(One difference we've made is adding $notifiable->email
to the body of the email.)
Now we need to tell Laravel to call our notification instead of the one found in the CanResetPassword
trait. To do this, just edit your App/User.php
model:
Add use App\Notifications\ResetPassword;
to the top of the file (ie. a link to the notification you just created) and then add a new method:
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPassword($token));
}
Finally we need to update our routes to include the new {email}
parameter:
Route::get('/password/reset/{token}/{email}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Note: Depending on your version of Laravel, you may need to manually create the auth
routes yourself, replacing the shortcut that Laravel creates (ie. Auth::routes()
). This is the entirity of what you need in your routes file:
// LOGIN
Route::get('/login', 'SessionController@create')->name('login');
Route::post('/login', 'SessionController@store');
// REGISTER
Route::get('/register/{accessCode?}', 'UserController@create')->name('register');
Route::post('/register', 'UserController@store');
// PASSWORD
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')
->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')
->name('password.email');
Route::get('password/reset/{token}/{email}', 'Auth\ResetPasswordController@showResetForm')
->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')
->name('password.update');
// EMAIL VERIFICATION
Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
Route::get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
Now if the user fills in the /password/reset
form, they will be sent your notification, with the additional email parameter.
This now works perfectly!
You can improve on the above by adding a bit of error handling. To do this, just pass the email to your form, to handle things if there's a problem:
Add the following method to Auth/ForgotPasswordController.php
:
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return back()->withErrors(
['email' => trans($response)]
)->withInput($request->only('email'));
}
And then place the following at the top: use Illuminate\Http\Request;
.
And you're done!
If you want, I'd also recommend modifying the semantic HTML in your views (regardless of your design). Go to resources/views/auth/passwords/reset.blade.php
and remove the autofocus
attribute from the email input element, and add readonly
. (I move the autofocus
to the first password input element myself.)
You can still tidy things up a bit more by encrypting the email in the URL. This isn't for security, it's to make it look prettier (so to speak) and make it less likely for the user to mess with the URL and break something.
To do this, just go to the notification we created in step 1 and change $notifiable->email
to encrypt($notifiable->email)
.
Then go to app/Http/Controllers/Auth/ResetPasswordController.php
and add the following method:
public function showResetForm(Request $request, $token = null)
{
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => decrypt($request->email)]
);
}
Don't forget to place use Illuminate\Http\Request;
at the top, too.
And you're done! (Again.)
I've tested all this fully, and it works perfectly, but if you have an issue, let me know.
Good luck!
Good recap 👍
Just a quick note, you can add the
email
parameter directly on theurl()
like this so you don't have to update the routes manually.Have a good day!