How to RateLimit forgot-password requests in Laravel Fortify?

we will need a pair of routes to handle allowing the user to request a password reset link via their email address.Second we will need a pair of routes.

How to RateLimit forgot-password requests in Laravel Fortify?

I've looked at this aswell, and the backend actually throttles too many requests for a specific email. However, that is done through 422 Unprocessable Entity (which is usually a validation error), like so:

{"message":"The given data was invalid.","errors":{"email":["Please wait before retrying."]}}

I've came across your question because I was looking to throttle /fortify/reset-password and /fortify/forgot-password (password.update/password.email) based on the IP only. Both required the user's email address and thus enables an attacker to test existing email addresses, as the backend will happily tell you if it exists or not—without rate limit! The existing rate limit mentioned above (422) will only kick in for multiple requests for one specific email. So this won't help if someone were to enumerate possible emails.

My solution is for reset-password and forgot-password (and rather a workaround). I will omit the reset-password as it is equal to forgot-password.

However, this requires you to overwrite fortify's published route in your web.php. No changes are made to any vendor files. You will have to ensure that after upgrading fortify that the route registration is still equal to the original (https://github.com/laravel/fortify/blob/master/routes/routes.php)

  1. Let's add a new rate limiter [app/Providers/FortifyServiceProvider.php]

This will allow 10 requests per minute per IP

public function boot()
    {
        // ...
        RateLimiter::for('forgot-password', function (Request $request) {
            return Limit::perMinute(10)->by($request->ip());
        });
    }
  1. Add it to your fortify config [config/fortify.php]
 'limiters' => [
        // ...
        'forgot-password' => 'forgot-password',
    ],
  1. Make sure that your fortify service provider is called before your own route service provider (which is the case when installed via official Laravel docs) [config/app.php]
'providers' => [

        // ...
        /*
         * Package Service Providers...
         */
        App\Providers\FortifyServiceProvider::class,


        /*
         * Application Service Providers...
         */

        // ...
        App\Providers\RouteServiceProvider::class,

    ],
  1. You can now overwrite the route and attach the rate limiter [routes/web.php]
use Laravel\Fortify\Http\Controllers\PasswordResetLinkController;

//...

$limiter = config('fortify.limiters.forgot-password');
// Copied from
// https://github.com/laravel/fortify/blob/c64f1e8263417179d06fd986ef8d716d4c5689e2/routes/routes.php#L55
// with addition of the throttle middleware.
// TODO: Keep this updated when updating fortify!
Route::post(config('fortify.prefix', 'fortify') . '/forgot-password', [PasswordResetLinkController::class, 'store'])
    ->middleware(['guest', 'throttle:' . $limiter])
    ->name('password.email');

More than 10 requests from one IP will now throw a 429 Too Many Requests from you backend to the client.

The nice thing about this is that you only overwrite the route-registration but can still use all fortify features as it uses the stock controller.


Click Here to Visit

What's Your Reaction?

like
0
dislike
0
love
0
funny
0
angry
0
sad
0
wow
0