6

Currently the logic behind Resetting Password is that user must provide valid/registered e-mail to receive password recovery e-mail.

In my case I don't want to validate if the e-mail is registered or not due to security concerns and I want to just do the check in back-end and tell user that "If he has provided registered e-mail, he should get recovery e-mail shortly".

What I've done to achieve this is edited in vendor\laravel\framework\src\Illuminate\Auth\Passwords\PasswordBroker.php sendResetLink() method from this:

 /**
     * 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;
    }

to this:

 /**
     * 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.
        if(!is_null($user)) {
            $user->sendPasswordResetNotification(
                $this->tokens->create($user)
            );
        }

        return static::RESET_LINK_SENT;
    }

This hard-coded option is not the best solution because it will disappear after update.. so I would like to know how can I extend or implement this change within the project scope within App folder to preserve this change at all times?

P.S. I've tried solution mentioned here: Laravel 5.3 Password Broker Customization but it didn't work.. also directory tree differs and I couldn't understand where to put new PasswordBroker.php file.

Thanks in advance!

richardev
  • 976
  • 1
  • 10
  • 33

3 Answers3

22

Here are the steps you need to follow.

Create a new custom PasswordResetsServiceProvider. I have a folder (namespace) called Extensions where I'll place this file:

<?php

namespace App\Extensions\Passwords;

use Illuminate\Auth\Passwords\PasswordResetServiceProvider as BasePasswordResetServiceProvider;

class PasswordResetServiceProvider extends BasePasswordResetServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerPasswordBroker();
    }

    /**
     * Register the password broker instance.
     *
     * @return void
     */
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            return new PasswordBrokerManager($app);
        });

        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }
}

As you can see this provider extends the base password reset provider. The only thing that changes is that we are returning a custom PasswordBrokerManager from the registerPasswordBroker method. Let's create a custom Broker manager in the same namespace:

<?php

namespace App\Extensions\Passwords;

use Illuminate\Auth\Passwords\PasswordBrokerManager as BasePasswordBrokerManager;

class PasswordBrokerManager extends BasePasswordBrokerManager
{
    /**
     * Resolve the given broker.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\PasswordBroker
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException(
                "Password resetter [{$name}] is not defined."
            );
        }

        // The password broker uses a token repository to validate tokens and send user
        // password e-mails, as well as validating that password reset process as an
        // aggregate service of sorts providing a convenient interface for resets.
        return new PasswordBroker(
            $this->createTokenRepository($config),
            $this->app['auth']->createUserProvider($config['provider'] ?? null)
        );
    }
}

Again, this PasswordBrokerManager extends the base manager from laravel. The only difference here is the new resolve method which returns a new and custom PasswordBroker from the same namespace. So the last file we'll create a custom PasswordBroker in the same namespace:

<?php

namespace App\Extensions\Passwords;

use Illuminate\Auth\Passwords\PasswordBroker as BasePasswordBroker;

class PasswordBroker extends BasePasswordBroker
{
 /**
     * 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.
        if(!is_null($user)) {
            $user->sendPasswordResetNotification(
                $this->tokens->create($user)
            );
        }

        return static::RESET_LINK_SENT;
    }
}

As you can see we extend the default PasswordBroker class from Laravel and only override the method we need to override.

The final step is to simply replace the Laravel Default PasswordReset broker with ours. In the config/app.php file, change the line that registers the provider as such:

'providers' => [
...
// Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
   App\Extensions\Passwords\PasswordResetServiceProvider::class,
...
]

That's all you need to register a custom password broker. Hope that helps.

Bahdcoder
  • 384
  • 2
  • 4
  • This is like solution above, yet it does work. It's still too messy but this is better than mine. Thank you. I will continue to search for more simplistic solution. – richardev Apr 30 '19 at 02:19
  • 1
    Thanks! You've written a clear and concise yet fully coded description of how to do this. I've wow got a custom broker that's resetting passwords against an API. Working fine in Laravel 5.6. – sifriday Jun 13 '19 at 16:30
  • 1
    For the record, this works in Laravel 9 – kevnk May 26 '22 at 13:31
  • Also, for the record - this is the way to solve this if you use Fortify, the accepted answer only works for something like Breeze where all the auth controllers are in your source code, and not in the framework. – Vidur Dec 24 '22 at 09:22
  • Also, you don't need to copy the entire class - you can simply copy over the one method in each class you need to override - `PasswordResetServiceProvider::registerPasswordBroker`, `PasswordBrokerManager::resolve` and `PasswordBroker::sendResetLink` – Vidur Dec 24 '22 at 09:23
9

The easiest solution here would be to place your customised code in app\Http\Controllers\Auth\ForgotPasswordController - this is the controller that pulls in the SendsPasswordResetEmails trait.

Your method overrides the one provided by that trait, so it will be called instead of the one in the trait. You could override the whole sendResetLinkEmail method with your code to always return the same response regardless of success.

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(
        $request->only('email')
    );

    return back()->with('status', "If you've provided registered e-mail, you should get recovery e-mail shortly.");
}
Dwight
  • 12,120
  • 6
  • 51
  • 64
-1

You can just override the sendResetLinkFailedResponse method in your ForgetPasswordController class.

protected function sendResetLinkFailedResponse(Request $request, $response)
{
    return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
}

We'll just send the successful response even if the validation failed.

mpskovvang
  • 2,083
  • 2
  • 14
  • 19