Skip to content

Instantly share code, notes, and snippets.

@JohnnyWalkerDigital
Last active August 31, 2021 07:21
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save JohnnyWalkerDigital/ce0fede18f8dadecd557341a36b94f8a to your computer and use it in GitHub Desktop.
Save JohnnyWalkerDigital/ce0fede18f8dadecd557341a36b94f8a to your computer and use it in GitHub Desktop.
Laravel: Fix password reset (User email not sent)

Here's how to overcome this common gotcha: The default password reset system not working out of the box with Laravel 5.x (as you're not sent the user's password), without having to alter altering vendor/core. Here's how to make it work as you'd expect it to without changing any vendor files.

1. Create own notification

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.)

2. Point to your notification

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));
    }

3. Update your routes

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');

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!

Additional: Handle errors

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!

Additional: Finesse the UX

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.)

Additional: Encrypt the email address in the URL

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!

@AliVeseli
Copy link

Declare this function inside a helper file somewhere

function get_user_by_token($token){
    $records =  DB::table('password_resets')->get();
    foreach ($records as $record) {
        if (Hash::check($token, $record->token) ) {
           return $record->email;
        }
    }
}

Then Inside the reset.blade.php hide the from-group of the email and in the input email paste this !

value="{{ old('email') ?? get_user_by_token($token) }}"

@C-Tully
Copy link

C-Tully commented Jun 13, 2019

Thank you @AliVeseli! I was having a very difficult time with exactly what your snippet does. Thank you!

Google Search: Laravel + password_resets + Get record by token

@JohnnyWalkerDigital
Copy link
Author

JohnnyWalkerDigital commented Jun 13, 2019 via email

@parzival404
Copy link

Thank you
I only found out that if you use the above path (without the encrypted email code), it will send you an error
I fixed it like this:

In ResetPasswordController.php

use Illuminate\Contracts\Encryption\DecryptException;
use Redirect;

public function showResetForm(Request $request, $token = null)
    {
        try { 
            $email = decrypt($request->email);
        } catch (DecryptException $e) { 
            return Redirect::to('/');
         
        }
        return view('auth.passwords.reset')->with(
            ['token' => $token, 'email' => $email]
        );
    }

@osamamoinchawla
Copy link

osamamoinchawla commented Jul 14, 2020

I am still having confusion! please help

Can't receive email

@parzival404
Copy link

I am still having confusion! please help

Can't receive email

You mean emails in general or to reset password?

@PanduMA
Copy link

PanduMA commented Dec 18, 2020

I am still having confusion! please help

Can't receive email
Yes, so am I, in my production is working but in dev email not sending. I dont know why

@ManishNewa
Copy link

I am still having confusion! please help
Can't receive email
Yes, so am I, in my production is working but in dev email not sending. I dont know why

same here, the code is working fine in prod but not working locally. The email is not triggered at all in local. I cant even find them in the jobs table as well. So much confusing

@mini2911
Copy link

I got reset password mail but when I clicked on reset password button It showing 404 error. I couldn't change password. Please let me What's wrong in my code. I followed all your steps.

Thankss

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment