DEV Community

Cover image for Stop API Abuse: Advanced Rate Limiting in Laravel
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Stop API Abuse: Advanced Rate Limiting in Laravel

The Scraping Burst Vulnerability

When you expose a valuable B2B SaaS API at Smart Tech Devs, you aren't just serving legitimate clients. You are constantly fending off aggressive data scrapers, rogue internal scripts, and micro-DDoS attacks. A client's poorly written cron job can accidentally fire 5,000 requests per second at your /api/reports endpoint, instantly saturating your database connection pool and taking your platform offline for everyone else.

Basic "Fixed Window" rate limiting often fails to protect you. If you limit a user to 100 requests per minute, a bot can send 100 requests at 0:59, and another 100 requests at 1:01. That is 200 requests in 2 seconds, effectively bypassing your limits and crashing your servers. To secure your perimeter, you must implement Sliding Window Rate Limiting backed by Redis.

The Solution: In-Memory Throttling

You cannot use a relational database to track rate limits; the write-overhead of tracking every single API request will crash the database itself. You must use an in-memory datastore like Redis. Laravel’s native RateLimiter facade, when paired with the Redis cache driver, automatically utilizes an advanced sliding window algorithm to ensure bursts are mathematically impossible.

Step 1: Architecting the Global Rate Limiter

We configure our rate limiting logic centrally in the RouteServiceProvider (or AppServiceProvider in Laravel 11). We define strict, tiered limits based on the user's subscription tier.


namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // 1. Define the 'api' rate limiter
        RateLimiter::for('api', function (Request $request) {
            
            // 2. Identify the user or default to their IP address
            $identifier = $request->user()?->id ?: $request->ip();

            // 3. Enterprise clients get higher throughput
            if ($request->user()?->isEnterprise()) {
                return Limit::perMinute(1000)->by($identifier);
            }

            // 4. Standard users are strictly throttled to 60 requests per minute.
            // Redis tracks this with microsecond precision, preventing end-of-minute burst attacks.
            return Limit::perMinute(60)->by($identifier)->response(function () {
                // 5. Return a clean, semantic 429 Too Many Requests response
                return response()->json([
                    'error' => 'Rate limit exceeded.',
                    'message' => 'Please back off and try again later.'
                ], 429);
            });
        });
    }
}

Step 2: Protecting Heavy Endpoints

Some endpoints (like generating a massive PDF export) require even stricter limits than the global API constraint. We can attach custom limiters directly to specific routes.


// routes/api.php

// Apply the global 'api' limiter
Route::middleware('throttle:api')->group(function () {
    Route::get('/users', [UserController::class, 'index']);
});

// Create a custom strict limiter for PDF generation (e.g., 3 per hour)
RateLimiter::for('pdf-exports', function (Request $request) {
    return Limit::perHour(3)->by($request->user()->id);
});

// Apply the strict limiter to the heavy route
Route::post('/export/pdf', [ExportController::class, 'pdf'])
    ->middleware('throttle:pdf-exports');

The Engineering ROI

By enforcing Redis-backed rate limiting, you transform your API from a fragile, easily overwhelmed service into an impenetrable fortress. You mathematically guarantee that no single client—whether malicious or just badly coded—can consume more than their fair share of your server's CPU, ensuring 100% uptime and perfectly predictable infrastructure costs.

Top comments (0)