Framework: [5.5] Throttling unauthenticated requests (via a token guard) results in unexpected behaviour

Created on 12 Oct 2017  Â·  8Comments  Â·  Source: laravel/framework

  • Laravel Version: 5.5.14
  • PHP Version: 7.1.8

Description:

When attempting to implement throttling on an API endpoint, attempting to set different throttle controls for unauthenticated vs authenticated requests results in unexpected behaviour whereby the throttle middleware will always use the "unauthenticated" rate.

Throttling for authenticated vs unauthenticated requests was added in 5.5 in https://github.com/laravel/framework/pull/19807

This appears due to a race condition, whereby the throttle is run either before or after the token authentication requirements.

Steps To Reproduce:

Set middleware group to throttle api requests
$middlewareGroups = [ 'api' => [ // allow 10 requests per minute for unauthenticated requests // allow 60 request per minute for authenticated requests 'throttle:10|60,1', 'bindings', ], ];

Set a route to require authentication
Route::get('/', 'IndexController') ->middleware(['auth:api', 'api'])

Attempting to hit the API endpoint results in the throttle requests middleware to always return the unauthenticated request limit, which is based on the request signature of an un-authenticated request.

This appears due to "request->user()" (https://github.com/laravel/framework/blob/bd352a0d2ca93775fce8ef02365b03fc4fb8cbb0/src/Illuminate/Routing/Middleware/ThrottleRequests.php#L75) not yet being set, due to authentication occuring after throttle processing.

Additionally, attempting to move the authentication to occur before the throttle results in a HTTP 401 response being created, but this will never trigger the throttle actions, as the 401 response is returned before hitting other middleware functionality.

Most helpful comment

Is there any solution to this other than implementing the above workaround? I'm seeing exactly the same issue on Laravel 5.8.

All 8 comments

Using the middleware auth:api means you're using the middleware auth with the parameter (in this case guard name) api. The api guard uses token to authenticate, and you're not passing any. You're never using the throttle middleware in your example.

@sisve / @GrahamCampbell — confirming that an api token is included in the request — additionally, using auth:api is expected, as this is an api endpoint where authentication is completed via the the api guard.

The issue relates to the auth guard throwing an authentications exception, with an HTTP 401 "Unauthorized" response error being returned.

https://github.com/laravel/framework/blob/bd352a0d2ca93775fce8ef02365b03fc4fb8cbb0/src/Illuminate/Auth/Middleware/Authenticate.php#L66

The exception behaviour stops request processing, including the throttle middleware. This prevents the throttle middleware from being considered in the request, resulting in only authenticated requests being throttled.

Additionally, changing the ordering of the middleware to use the throttle middleware prior to the auth guard will result in the throttling middleware logic being based on an unauthenticated request, as the auth middleware has not run yet.

The problem itself is more-so a race condition, whereby we want to consider if the request is authenticated to determine the throttle limits, but authentication needs to occur prior to this and the guard stops processing when a failed authentication occurs.

@GrahamCampbell - can we get this re-opened please?

FYI — Our current workaround has been to create a new authentication "attempt" middleware that extends https://github.com/laravel/framework/blob/5.5/src/Illuminate/Auth/Middleware/Authenticate.php

removing the exception from being thrown in the "attempt" middleware class

https://github.com/laravel/framework/blob/bd352a0d2ca93775fce8ef02365b03fc4fb8cbb0/src/Illuminate/Auth/Middleware/Authenticate.php#L66

This essentially turns the middleware into an authentication attempt, with the existing framework provided middleware responsible for throwing the exception.

There's likely a cleaner / better way to approach this - the key issue being that we need an auth attempt to occur prior to the throttle logic, to ensure the throttle is based on whether the request was authenticated vs unauthenticated.

At the same time, we want the authentication middleware to throw the exception in most cases - as we rely on this to prevent unauthenticated access..

Kernel.php

protected $middlewareGroups = [
    ....
    'api' => [
        'auth.attempt:api',
        'throttle:10|60,1',
        'auth:api',
        'bindings',
    ],
    ....
];
...

protected $routeMiddleware = [
    'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
    'auth.attempt' => \App\Http\Middleware\Authenticate::class,
    ...
];

Is there any solution to this other than implementing the above workaround? I'm seeing exactly the same issue on Laravel 5.8.

Any news on this?

Any chance we could reopen this? This is still an issue in Laravel 7.15.0.

I had the same problem and was because I was sending an Authorization: Bearer null header from the client.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gabriellimo picture gabriellimo  Â·  3Comments

Anahkiasen picture Anahkiasen  Â·  3Comments

felixsanz picture felixsanz  Â·  3Comments

ghost picture ghost  Â·  3Comments

SachinAgarwal1337 picture SachinAgarwal1337  Â·  3Comments