whoacowboy
whoacowboy

Reputation: 7447

Throttle issue with server accessing a Laravel API

I have an API that is using Laravel that is being called from another instance of Laravel with Guzzle.

The second server's IP address is triggering the throttle on the API.

I would like to pass through the user's domain and IP address from the second server to the API. I am hoping not to recode the Throttle middleware.

I am wondering if anyone has faced this before and if so how they solved it.

The middleware group on the API is set up like this

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'api' => [
        'throttle:60,1',
        \Barryvdh\Cors\HandleCors::class,
        'bindings',
    ],
];

relevant throttle code

/**
 * Resolve request signature.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return string
 *
 * @throws \RuntimeException
 */
protected function resolveRequestSignature($request)
{
    if ($user = $request->user()) {
        return sha1($user->getAuthIdentifier());
    }
    if ($route = $request->route()) {
        return sha1($route->getDomain().'|'.$request->ip());
    }
    throw new RuntimeException('Unable to generate the request signature. Route unavailable.');
}

Upvotes: 4

Views: 3209

Answers (3)

Diogo Sgrillo
Diogo Sgrillo

Reputation: 2701

The out of the box solution, if you are using a version >= 5.6, is to use the dynamic rate limit.

Dynamic Rate Limiting

You may specify a dynamic request maximum based on an attribute of the authenticated User model. For example, if your User model contains a rate_limit attribute, you may pass the name of the attribute to the throttle middleware so that it is used to calculate the maximum request count:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

The relevant part of the code

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

Thus, you could add a rate_limit property in the user (representing the second server) and pass a bigger number

EDIT:

If you don't want to have the caller authenticated, you can easily overwrite the resolveMaxAttempts method to calculate the limit dynamically based on the request data (you could use any parameter, the host, the ip, etc):

protected function resolveMaxAttempts($request, $maxAttempts)
{
    if (in_array(request->ip(), config('app.bypassThrottleMiddleware')) {
        return PHP_INT_MAX;
    }

    return parent::resolveMaxAttempts($request, $maxAttempts);
}

and in your config/app.php add:

'bypassThrottleMiddleware' => ['0.0.0.0'],

Upvotes: 0

Roland Starke
Roland Starke

Reputation: 1718

You can pass the client's IP address with the X_FORWARDED_FOR header, that way the IP address of the second server is not blocked.

Route::get('/', function (Request $request) {

    $client = new \GuzzleHttp\Client();

    $request = $client->request('GET', '/api/example', [
        'headers' => ['X_FORWARDED_FOR' => $request->ip()]
    ]);

    $response = $request->getBody();

});

On your main server you need to add your second server as a trusted proxy (docs) to App\Http\Middleware\TrustProxies in order to take the IP from this header.

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array
     */
    protected $proxies = [
        '192.168.1.1', // <-- set the ip of the second server here 
    ];

    //...
}

Now every call to $request->ip() on the main server will have the original client IP instead of the second server's IP. That will also affect the throttling.

Upvotes: 6

Fatemeh
Fatemeh

Reputation: 1

if ($route = $request->route()) {
    return sha1($route->getDomain().'|'.$request->ip());

Upvotes: -3

Related Questions