Closed rafaelrenanpacheco closed 8 years ago
In terms of UX it seems quite annoying to ask the user to type their password in again, but it's undoubtedly less secure. I guess it depends on how important security is in your app.
Side note: I've updated my answer above. I had previously missed out some minor steps (which could be deduced, but I've explicitly mentioned them now), and I've successfully tested it with a fresh installation of Laravel 5.6.
@khalilst @Mayonado @zmonteca
I have fixed it by editing/hacking
ResetPassword.php
notification &reset.blade.php
file. Now this does mean you are editing vendor file, so it's your choice to do this or not.ResetPassword.php
The file can be found at (5.4):
../vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php
Amend the
toMail
method:From:
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('app.url').route('password.reset', $this->token, false))) ->line('If you did not request a password reset, no further action is required.'); }
To
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('app.url') . route('password.reset', [$this->token, 'email=' . $notifiable->email], false))) ->line('If you did not request a password reset, no further action is required.'); }
Notice the updated parameters argument,
$notifiable
resolves toUser
object hence you can access the email property:[$this->token, 'email=' . $notifiable->email]
reset.blade.php
The file can be found at (5.4):
../resources/views/auth/passwords/reset.blade.php
The HTML may slightly differ here based on your CSS framework. In addition, you may want to show email field as read-only or make it hidden input. I prefer the user seeing the email they requested the password reset for.
From
<input type="text" id="email" name="email" class="input {{ $errors->has('email') ? ' is-danger' : '' }}" value="{{ old('email') }}">
To
<input type="text" id="email" name="email" class="input {{ $errors->has('email') ? ' is-danger' : '' }}" value="{{ $email }}" readonly>
Notice the updated value attribute, added the readonly attribute to stop user amending the email.
... value="{{ $email }}" readonly>
Hope this helps others.
Now, you don't need to override the vendor file of Notification. One way to override the toMail
method is creating a service provider like this:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;
use Illuminate\Support\Facades\Lang;
use Illuminate\Notifications\Messages\MailMessage;
class NotificationServiceProvider extends ServiceProvider
{
public function boot()
{
ResetPasswordNotification::toMailUsing(function ($notifiable, $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('web.password.reset', [$token, 'email=' . $notifiable->email], false)))
->line(Lang::getFromJson('If you did not request a password reset, no further action is required.'));
});
}
}
Then, registry your service provider in config/app.php
:
<?php
return [
# ...
'providers' => [
# ...
App\Providers\NotificationServiceProvider::class
]
];
These work arounds are great, but it is a huge waste of everyone's cumulative time to have to implement a workaround for a framework.
Check out what the reset password form looks like for Hulu.com, notice they do not require you to type in your email address. I looked at the logic for Laravels PW reset and it seems we cannot omit the email address from the form. If you could check the hash in the DB it would work, but MySQL does not support bcrypt. Does this mean that hulu us hashing their tokens using a MySQL supported hash algorythm such as SHA?
Is there an issue with storing the plain text token in the DB?
The solution I posted here solves this problem (at least from a UX perspective) by hashing the user’s email and hiding it in the URL.
@khalilst @Mayonado @zmonteca
I have fixed it by editing/hacking
ResetPassword.php
notification &reset.blade.php
file. Now this does mean you are editing vendor file, so it's your choice to do this or not.ResetPassword.php
The file can be found at (5.4):
../vendor/laravel/framework/src/Illuminate/Auth/Notifications/ResetPassword.php
Amend the
toMail
method:From:
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('app.url').route('password.reset', $this->token, false))) ->line('If you did not request a password reset, no further action is required.'); }
To
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('app.url') . route('password.reset', [$this->token, 'email=' . $notifiable->email], false))) ->line('If you did not request a password reset, no further action is required.'); }
Notice the updated parameters argument,
$notifiable
resolves toUser
object hence you can access the email property:[$this->token, 'email=' . $notifiable->email]
reset.blade.php
The file can be found at (5.4):
../resources/views/auth/passwords/reset.blade.php
The HTML may slightly differ here based on your CSS framework. In addition, you may want to show email field as read-only or make it hidden input. I prefer the user seeing the email they requested the password reset for.
From
<input type="text" id="email" name="email" class="input {{ $errors->has('email') ? ' is-danger' : '' }}" value="{{ old('email') }}">
To
<input type="text" id="email" name="email" class="input {{ $errors->has('email') ? ' is-danger' : '' }}" value="{{ $email }}" readonly>
Notice the updated value attribute, added the readonly attribute to stop user amending the email.
... value="{{ $email }}" readonly>
Hope this helps others.
You're a genius. I was still facing this issue with laravel 5.6
Just add the following code in Model User.php
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token.'/'.$this->email));
}
I faced this issue with laravel 5.7
The fix was very simple, update file resources\views\auth\passwords\reset.blade.php
:
change the line from:
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email or old('email') }}" required autofocus>
to
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus>
notice the difference is the little 'or' to '??'
I faced this issue with laravel 5.7
The fix was very simple, update file
resources\views\auth\passwords\reset.blade.php
:change the line from:
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email or old('email') }}" required autofocus>
to
<input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ $email ?? old('email') }}" required autofocus>
notice the difference is the little 'or' to '??'
It's not working
It's not working
Just follow the my guide above. It's simple and it works without having to alter your vendor folder (which you really shouldn't do).
Add
use App\Notifications\ResetPassword;
to the top of the file (ie. a link to the notification you just created)
Thanks for this great and proper solution that works for me in 5.8.
Just a note : personnaly i needed to call
use App\Notifications\ResetPassword as ResetPasswordNotification;
instead to properly call the new notification instead of the vendor one.
I may be mistaken, but asking for an e-mail address in the password change form itself allows the form to be brute-forced (even if it's throttled). Basically, if someone guesses an e-mail address, will be able to get access to the site.
Asking for an e-mail address in the form defeats the purpose of the token.
In the current state of things, the token is useless (it only tells the router to load a different controller method if it is present).
I went on a solution where I overrode the trait in the controller itself, and retrieved the e-mail address with a token. This comes with a slight performance impact, as it needs to go through the password reset table to match the token, but on the site it's going to be used, it won't matter.
It doesn't defeat the purpose. Just another level, maybe unnecessary, of security. The email must match the email that initiated the reset an the token must match the token stored against the email address. Brute force isn't possible in this case.
There’s a token which needs to match the email address. Having an email address isn’t enough to change someone’s password.
On 3 May 2019, at 12:16, Ervin Nagy notifications@github.com wrote:
I may be mistaken, but asking for an e-mail address in the password change form itself allows the form to be brute-forced (even if it's throttled). Basically, if someone guesses an e-mail address, will be able to get access to the site.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
For everyone who doesn't want to touch vendor or create a lot of extra code, there's a small simple dirty JS solution:
var url = window.location.href;
if (url.indexOf("/password/reset/") >= 0) {
var email = url.substr(url.indexOf('?email=') + 7);
$("#email").val(decodeURIComponent(email));
}
Checks if you're on the password reset page and copies the email address from the URL in the email address box.
Description:
When sending the password reset e-mail in Laravel 5.3, the reset link have a token, but doesn't have the user e-mail. This way, the reset form will not load the user e-mail. In 5.2, the reset link had the user e-mail.
Without the user e-mail in the URL,
Illuminate\Foundation\Auth\ResetsPasswords.php
will send a null e-mail inshowResetForm
, because$request->email
will evaluate to null. Then, the reset form provided by the framework fromIlluminate\Auth\Console\stubs\make\views\auth\passwords\email.stub
will show a blank e-mail in the following input:Digging up who missed sending the e-mail in the URL, we can found a
Illuminate\Auth\Notifications\ResetPassword.php
which does the following action:As you can see, by default Laravel does not add the e-mail in the request URL. Since this trait usually is overwritten, we can add the e-mail to the URL to make things working again. The issue here is that, by default, Laravel is missing this e-mail in the request url.
Steps To Reproduce:
Send a reset e-mail link and open the link provided in the e-mail.