Throttle Password Reset

Posted: 2016-10-16 15:07:55

Laravel Throttle works on failed auth attempts but how about password resets?

Here is what I made my app/Http/Controllers/Auth/PasswordController.php look like

5.3

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class PasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset requests
    | and uses a simple trait to include this behavior. You're free to
    | explore this trait and override any methods you wish to tweak.
    |
    */

    use ResetsPasswords, ThrottlesLogins;

    protected $username;
    /**
     * Create a new password controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * Validate the request of sending reset link.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function validateSendResetLinkEmail(Request $request)
    {
        $this->incrementLoginAttempts($request);

        $this->username = 'email';

        if($this->hasTooManyLoginAttempts($request)) {
            return back()
                ->withError("You have exceeded the limit of attempts. Please try again shortly")
                ->withInput();
        }

        $validator = Validator::make(
            $request->all(), [
                'email' => 'required|email'
            ]
        );

        if ($validator->fails()) {
            return back()
                ->withMessage("If you are in the system you will get an email shortly to reset your password")
                ->withInput();
        }
    }

    public function loginUsername()
    {
        return property_exists($this, 'username') ? $this->username : 'email';
    }

    protected function getThrottleKey(Request $request)
    {
        return $request->ip();
    }

}

5.2

The controller ended up looking like this

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Password;
class PasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Password Reset Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password reset requests
    | and uses a simple trait to include this behavior. You're free to
    | explore this trait and override any methods you wish to tweak.
    |
    */
    use ResetsPasswords, ThrottlesLogins;

    protected $username;
    /**
     * Create a new password controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }
    /**
     * Validate the request of sending reset link.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function sendResetLinkEmail(Request $request)
    {
        $this->incrementLoginAttempts($request);
        $this->username = 'email';
        Log::debug("Validate");

        if($this->hasTooManyLoginAttempts($request)) {
            return redirect()->back()->withErrors(['email' => "You have exceeded the limit of attempts. Please try again shortly"]);
        }

        $this->validate($request, ['email' => 'required|email']);

        $broker = $this->getBroker();

        $response = Password::broker($broker)->sendResetLink(
            $request->only('email'), $this->resetEmailBuilder()
        );

        switch ($response) {
            case Password::RESET_LINK_SENT:
                return $this->getSendResetLinkEmailSuccessResponse($response);

            case Password::INVALID_USER:
            default:
                return $this->getSendResetLinkEmailFailureResponse($response);
        }

    }

    public function loginUsername()
    {
        return property_exists($this, 'username') ? $this->username : 'email';
    }

    protected function getThrottleKey(Request $request)
    {
        return $request->ip();
    }
}

TIP: While your add it leak even less about your app's user emails

On other thing here is take a moment to change your error message resources/lang/en/passwords.php

    'password' => 'Passwords must be at least six characters and match the confirmation.',
    'reset' => 'Your password has been reset!',
    'sent' => 'We have e-mailed your password reset link!',
    'token' => 'This password reset token is invalid.',
    'user' => "If you are a user in the system an a reset email has been sent",

Where user does not leak out any information about the email being correct or not.