Laravel 8 Fortify login says 429 TOO MANY REQUESTS

Viewed 10433

I'm having a problem related to 429 TOO MANY REQUESTS. I have used Laravel fortify and my web route is like

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard','DashboardController@dashboardView')
    ->name('dashboard')->middleware('auth');

The problem is coming intermittently, after successful login I redirected to dashboard if immediately I hit logout and instantly try to log-in it is giving 429 TOO MANY REQUESTS and in the address bar the URL is http://127.0.0.1:8000/login. Now if I wait there for a sec and refresh the page it redirects to the dashboard page.

I have searched the web, everyone is saying about throttle and I'm not seeing this as the solution. Please help me. Thanks.

3 Answers

I stumbled upon the same problem today and did some debugging. When registering the /login route, Fortify applies the Illuminate\Routing\Middleware\ThrottleRequests:login middleware to it. This means, for every request to that route, the ThrottleRequests middleware will call the RateLimiter instance for that specified key. Apparently, Fortify doesn't register a RateLimiter for the login key.

Due to the missing key in the $limiters property of the RateLimiter instance, the ThrottleRequests middleware uses its default fallback, which doesn't handle the edge case "there SHOULD be a rate limiter for that key, but there isn't." really well. The $maxAttempts variable is set to 0 and will result in flaky rate limiting behaviour.

I feel like this is a bug in Fortify, because rate limiting is also happening in the \Laravel\Fortify\Actions\EnsureLoginIsNotThrottled action, which is invoked in the \Laravel\Fortify\Http\Controllers\AuthenticatedSessionController controller. I didn't check this on a fresh Laravel installation, though, so I don't want to jump to conclusions here.

Anyway, long story short: As a workaround, you can simply register a rate limiter for the "login" key in some of your providers, e. g. AppServiceProvider or AuthServiceProvider:

public function boot()
{
    RateLimiter::for("login", function () {
        Limit::perMinute(5);
    });
}

Edit: I just realized that the rate limiter for the "login" key is indeed provided by Fortify within the FortifyServiceProvider class. If you happen to have a problem similar to the one discussed above, make sure that you added the FortifyServiceProvider class to your providers array in the config/app.php.

I tried everything including the Best Answer but it just didn't work.

Therefore, not even changing the RateLimiter in the FortifyServiceProvider class.

I'd try to log in and get a 429 error after just one login attempt.

Here what was the issue for me, it was the the config/fortify.php file.

I had to change:

    /*
    |--------------------------------------------------------------------------
    | Rate Limiting
    |--------------------------------------------------------------------------
    |
    | By default, Fortify will throttle logins to five requests per minute for
    | every email and IP address combination. However, if you would like to
    | specify a custom rate limiter to call then you may specify it here.
    |
    */

    'limiters' => [
        'login' => 'login',
        'two-factor' => 'two-factor',
    ],

to

    /*
    |--------------------------------------------------------------------------
    | Rate Limiting
    |--------------------------------------------------------------------------
    |
    | By default, Fortify will throttle logins to five requests per minute for
    | every email and IP address combination. However, if you would like to
    | specify a custom rate limiter to call then you may specify it here.
    |
    */

    'limiters' => [
        'login' => 5,
        'two-factor' => 5,
    ],

And funny enough the problem is inherent in the Fortify package itself when you run:

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider" as per their documentation instructions.

This fundamental reason being that code within vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php is not able to correctly parse the limit:

    /**
     * Resolve the number of attempts if the user is authenticated or not.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int|string  $maxAttempts
     * @return int
     */
    protected function resolveMaxAttempts($request, $maxAttempts)
    {
        if (Str::contains($maxAttempts, '|')) {
            $maxAttempts = explode('|', $maxAttempts, 2)[$request->user() ? 1 : 0];
        }

        if (! is_numeric($maxAttempts) && $request->user()) {
            $maxAttempts = $request->user()->{$maxAttempts};
        }

        return (int) $maxAttempts;
    }

, which means, 'login' is just parsed as 0 and that's what it returns.

Now I don't have to run php artisan cache:clear just to test.

go to

app/http/kernel.php

and remove from routeMiddleware list the line

'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
Related