Laravel API rate limiter for specific method - php

Is there any way to apply rate limit (throttle) for specific method like POST, PUT to prevent multiple api hitting within seconds
I tried to apply limit for whole api in /app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
...
],
'api' => [
'throttle:1,0.008', <<--- its prevent all api within 0.48 sec
],
];
Problem :
we need to prevent only selected methods.

There are number of ways to do it, You can create another middleware which you can use and group the routes you want to apply custom throttle.
Additionally, you can straightly apply the throttle when defining a route
Route::post('/wiggle', function () {
//
})->middleware(['auth:api','throttle:1,0.008']);
or
Route::middleware(['auth:api','throttle:1,0.008'])->group(function () {
Route::post('wiggle', [YourClass::class, 'wiggle'])->name('wiggle');
});

You can use multiple ways to make rate limit in Laravel.
One of ways is Middleware. silver already describe the way.
Second way is using Illuminate\Support\Facades\RateLimiter (Laravel 8 or higher)
For example, if you want to send email verification messages with rate limit 1 message per 60 seconds.
namespace App\Http\Controllers;
use Illuminate\Support\Facades\RateLimiter;
class EmailVerificationController extends Controller
{
public function send(Request $request)
{
$user = Auth::user();
$email = $request->input('email');
$resendSmsTimeoutSecs = 60;
$rateLimiterKey = 'email-verification:' . $email;
RateLimiter::attempt($rateLimiterKey, 1,
function () use ($user) {
$user->sendEmailVerification();
},
$resendSmsTimeoutSecs
);
return response()->json([
'resend_timeout' => RateLimiter::availableIn($rateLimiterKey)
]);
}
}
About RateLimiter

Related

Laravel 7 - cannot fake 3rd party Http call using a Middleware

I am trying to use a middleware in Laravel 7 to fake Http calls to a 3rd party API. So I can assign that middleware to any route which will make calls to that 3rd party API. So whenever that route is called, it will call to the faked API.
Purpose of this is, when I want to fake the API, I just only have to assign the middleware to the route. When I don't want to fake the API, I will just remove the middleware from the route.
The middleware looks like below.
namespace App\Http\Middleware;
class MockApiEndpoints
{
public function handle($request, Closure $next)
{
// mock api/endpoint
$vacancies = $this->vacancyRepository->all();
$url = 'api/endpoint';
$sessionId = Str::uuid()->toString();
$response = [
'result' => 'OK',
'content' => [
'suggestedVacancies' => array_map(function ($uid) {
return [
'id' => $uid,
'relevance' => bcdiv(rand(9, 99), 100, 2)
];
}, $vacancies->pluck('uid')->all()),
'sessionId' => $sessionId
]
];
$this->mockHttpRequest($url, $response);
return $next($request);
}
protected function mockHttpRequest(string $uri, $response, int $status = 200, array $headers = [])
{
$url = config('api.base_url') . '/' . $uri;
Http::fake([
$url => Http::response($response, $status, $headers)
]);
}
}
Even though I attach this this middleware to the route, route still makes calls to the original API. So the Htpp::fake doesn't work in the middleware it seems. Htpp::fake does work if I use it inside the controller.
Middleware is attached to the route as below. (Middleware is properly registered in the $routeMiddleware array in app/Http/Kernal.php)
namespace App\Providers;
class RouteServiceProvider extends ServiceProvider
{
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware(['MockApiEndpoints'])
->namespace($this->namespace . '\Api')
->group(base_path('routes/api.php'));
}
}
I got my work done by using a workaround. But I want to know why does Http::fake doesn't work in middleware. Thank you for your answers in advance.
Instead of returning $next($request) you should return a response(...) in your middleware.
Perhaps just forward the response from the fake call.
return response($responseFrom3rdPartyApiCall, 200);

Laravel Validation - Rule to disallow request parameters

In my Laravel 5.8 app I have many API routes which return paginated results. If I make a request to my API appending the following query string I can disable pagination.
http://api.test/users/?no_paginate=1
My question is... how can I disable no_paginate from being used on certain routes? I'd preferbly want some validation to go in the request class but I can't find anything in the docs for that.
You can do this using a Global Middleware.
Create a DisableNoPaginate Middleware:
php artisan make:middleware DisableNoPaginate
Then define what the middleware should do (DisableNoPaginate.php):
<?php
namespace App\Http\Middleware;
use Closure;
class DisableNoPaginate
{
public function handle($request, Closure $next)
{
//remove no_paginate param from request object
unset($request['no_paginate']);
return $next($request);
}
}
Arrange for the middleware to run on all routes (routes.php):
$app->middleware([
App\Http\Middleware\DisableNoPaginate::class
]);
Now the no_paginate query param should be stripped from all your incoming requests.
For the best approach to get users either paginate or get all listing by below code in UsersController
public function index($type = null, Request $request)
{
$builder = User::where(/*query*/);
if($type == "paginate") {
$items = $builder->paginate(10);
} else {
$items = $builder->get();
}
return view("users.index", ['users' => $items]);
}
Here is the route in web.php/api.php file
Route::get('/{type?}', ['as' => 'users.index', 'uses' => 'UsersController#index']);
Here url will be
http://api.test/users/paginate // get pagination response.
http://api.test/users // get response without pagination
I think this will help you.

Throttling comments

I want to make it so a user can only post 1 comment per minute at most.
I've tried simply using the throttle middleware and it is not working. I can still post comments every second.
Route code:
Route::post('comment/{id}', 'HomeController#comment')->name('comment')->middleware('throttle');
Controller code:
public function comment($id)
{
$this->validate(request(), [
"body" => "required",
]);
$jersey = Jersey::findOrFail($id);
$comment = new Comment;
$comment->user_id = auth()->user()->id;
$comment->jersey_id = $jersey->id;
$comment->body = request()->input('body');
$comment->save();
activity()->by(auth()->user())->withProperties($comment)->log('Commented');
request()->session()->flash('status', 'Comment submitted!');
return redirect()->route('concept', $id);
}
How do I make it so that it will flash an error instead of saving if the user is attempting to post more than 1 comment per minute?
Usually I'm using throttle in route group like that:
Route::group(['middleware' => 'throttle:1'], function () {
// Your routes here
Route::get('/', 'HomeController#comment')->name('comment');
// ...
)}
But in your case you can modify your code with specifying throttle parameters like that:
Route::post('comment/{id}', 'HomeController#comment')->name('comment')->middleware('throttle:1');
Don't forget to clear caches to apply changes.
I ended up using the https://github.com/GrahamCampbell/Laravel-Throttle package.

Laravel Throttle Middleware: is there a callback / handler for it?

I want to use Laravel's Throttle Middleware, but I want to also log any cases where the user made too many attempts.
Is there any Callback / Handler where I can detect this and do something with it?
Let's say I have a route like this:
Route::get('foo', array('before' => 'throttle:5,10', function () {
return 'You shall pass!';
}, ));
If it reaches the max count, it will fire this event:
Illuminate\Auth\Events\Lockout
So you can probably listen to it
protected $listen = [
...
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
];
Illuminate\Auth\Events\Lockout event is only fired in Illuminate\Foundation\Auth\ThrottlesLogins but you can still fire it by hand.
This is how I've done that:
In app/Exceptions/Handler.php add these use statements:
use Illuminate\Auth\Events\Lockout;
use Symfony\Component\HttpKernel\Exception\HttpException;
Add this to report method of the same file:
if ($exception instanceof HttpException) {
if ('Too Many Attempts.' == $exception->getMessage()) {
event(new Lockout(request()));
}
}
Execute php artisan make:listener LogThrottleLimitReached and add this code inside handle method of app/Listeners/LogThrottleLimitReached.php file:
$request = $event->request;
\Log::error(sprintf(
'Throttling rate limit reached. URL: %s, Body: %s, IP: %s',
$request->url(),
json_encode($request->all()),
$request->ip()
));
Register that listener in app/Providers/EventServiceProvider.php:
use App\Listeners\LogThrottleLimitReached;
use Illuminate\Auth\Events\Lockout;
and
protected $listen = [
Lockout::class => [
LogThrottleLimitReached::class,
],
];
You can just extend the ThrottleRequests class and redeclare its buildException, or try handling its exception (\Symfony\Component\HttpKernel\Exception\HttpException with 429 'Too Many Attempts.') inside app/Exceptions/Handler.php.

Cache entire HTML response in Laravel 5

I am trying to cache entire response using middleware
Steps i followed
Generated two middleware
AfterCacheMiddleware
BeforeCacheMiddleware
With in BeforeCacheMiddleware:
public function handle($request, Closure $next)
{
$key = $request->url();
if(Cache::has($key)) return Cache::get($key);
return $next($request);
}
With in AfterCacheMiddleware
public function handle ($request, Closure $next)
{
$response = $next($request);
$key = $request->url();
if (!Cache::has($key)) Cache::put($key, $response->getContent(), 60);
return $response;
}
Registered middleware in $routeMiddleware array of kernal.php
'cacheafter' => 'App\Http\Middleware\AfterCacheMiddleware',
'cachebefore' => 'App\Http\Middleware\BeforeCacheMiddleware',
With in routes.php i am calling this dummy routes like this
Route::get('middle', ['middleware' => 'cachebefore', 'cacheafter', function()
{
echo "From route";
}]);
Issue:
only cachebefore middleware is getting called. cacheafter is not getting called at all
Can anyone suggest what i am missing here ?
I found this question while I was looking for solution myself. I am aware that there is the Flatten package which does this caching, but I couldn't find nice examples on how to do this by myself. The solution attempt in this question contains ideas that were useful for my own solution, although I chose to go with a single middleware only.
Although the question is old and asker probably doesn't need the answer anymore, I will share my solution here as I feel that SO (and internet) lacks this caching sample for Laravel 5. I will try to explain as much as I can, but for most benefit you should be familiar with Routing, Caching and Middlewaring in Laravel 5. So here goes the solution:
Middleware
Create a middleware, those are usually placed in app/Http/Middleware folder and I will call the file CachePage.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Cache;
class CachePage
{
public function handle($request, Closure $next)
{
$key = $request->fullUrl(); //key for caching/retrieving the response value
if (Cache::has($key)) //If it's cached...
return response(Cache::get($key)); //... return the value from cache and we're done.
$response = $next($request); //If it wasn't cached, execute the request and grab the response
$cachingTime = 60; //Let's cache it for 60 minutes
Cache::put($key, $response->getContent(), $cachingTime); //Cache response
return $response;
}
}
Change $key according to your needs... You have all the $request with all the parameters... Change Cache::put($key, $value, $minutes) to Cache::forever($key, $value) if you will clear the cache manually and don't want it to ever expire.
Using the URL as key for storing cache is usable in most cases, but one might think of something more appropriate for a certain project.
Registering middleware
Register it in app/Http/Kernel.php by adding the middleware to $routeMiddleware array like this:
protected $routeMiddleware = [
/* ... */
/* Other middleware that you already have there */
/* ... */
'cachepage' => \App\Http\Middleware\CachePage::class,
];
Of course, you should change \App\Http\Middleware\CachePage if you placed it elsewhere or gave it another name. Also the key name cachepage is up to you - it will be used to invoke the middleware.
Usage
In your app/Http/routes.php use the middleware just like auth or other middlewares, for example, you might make a route group for all the pages that should be cached:
Route::group(['middleware' => 'cachepage'], function ()
{
Route::get('/', 'HomeController#home');
Route::get('/contacts', 'SectionController#contacts');
});
The list of middlewares has to be inside square brackets:
Route::get('middle', ['middleware' => ['cachebefore', 'cacheafter'], function()
{
echo "From route";
}]);

Categories