How to Fix Missing Rate Limiting in Laravel
Your Laravel login and API endpoints have no rate limiting, enabling brute-force attacks and API abuse. Add throttling now.
Credential stuffing and brute-force attacks have reached industrial scale. Billions of username/password combinations from previous breaches are freely available in combo lists traded on hacking forums. Automated tools test these combinations against login endpoints at thousands of requests per minute — not searching for a specific user's password, but systematically trying known credentials across millions of targets simultaneously. Even if your users use unique passwords, a login endpoint with no rate limiting is an open invitation for these automated campaigns.
The economics strongly favor the attacker. Running a credential stuffing campaign against a Laravel application with no rate limiting costs almost nothing — consumer hardware can make thousands of requests per minute, and residential proxy networks obscure the source IP. For the attacker, even a 0.1% hit rate across a large user base yields hundreds of valid credentials that can be monetized through account takeover, data harvesting, or resale to other actors.
Beyond credential attacks, the absence of rate limiting enables API abuse at scale: competitors scraping your data, automated submission of contact forms for spam, exhaustion of your third-party API quota (resulting in unexpected bills), and server resource depletion that constitutes a denial of service. Laravel's built-in throttle middleware using Redis provides the necessary infrastructure, but it must be explicitly applied — it is not enabled by default on any routes. The login route is the highest-priority target, but password reset endpoints, registration forms, and any rate-billed API calls are equally important.
The Problem
Missing rate limiting on login pages, API endpoints, and form submissions allows attackers to make unlimited requests to your application. This enables brute-force password attacks, credential stuffing, API abuse, and can lead to denial of service. Without rate limiting, an attacker can attempt thousands of login combinations per minute or exhaust your server resources with automated requests.
How to Fix
-
1
Define rate limiters in your application
In Laravel 11+, configure rate limiters in bootstrap/app.php or a service provider. In AppServiceProvider boot():
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter;public function boot(): void { RateLimiter::for('login', function (\Illuminate\Http\Request $request) { return Limit::perMinute(5)->by($request->ip()); });RateLimiter::for('api', function (\Illuminate\Http\Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); });RateLimiter::for('contact', function (\Illuminate\Http\Request $request) { return Limit::perMinute(3)->by($request->ip()); }); } -
2
Apply rate limiting to routes
Add the throttle middleware to your routes:
// Login routes Route::post('/login', [AuthController::class, 'login']) ->middleware('throttle:login');Route::post('/forgot-password', [PasswordController::class, 'forgot']) ->middleware('throttle:login');// API routes Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () { Route::get('/user', fn () => auth()->user()); // ... other API routes });// Contact form Route::post('/contact', [ContactController::class, 'submit']) ->middleware('throttle:contact'); -
3
Add rate limiting response headers
Laravel automatically includes rate limit headers in responses when using the throttle middleware:
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 59 Retry-After: 60 (when limit is exceeded)Customize the response when the limit is exceeded:
RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60) ->by($request->user()?->id ?: $request->ip()) ->response(function (Request $request, array $headers) { return response()->json([ 'message' => 'Too many requests. Please try again later.', ], 429, $headers); }); });
How to Verify
Test rate limiting by exceeding the limit. For the login endpoint:
for i in {1..10}; do curl -s -o /dev/null -w "%{http_code}\n" -X POST https://yourdomain.com/login -d "email=test@test.com&password=wrong"; done
After 5 requests, you should see 429 (Too Many Requests) status codes instead of the normal response.
Prevention
Include rate limiting in your route definitions from the start. Apply strict limits (5/minute) to authentication endpoints and moderate limits (60/minute) to API endpoints. Use StackShield to test that rate limiting is active on your public-facing endpoints.
Frequently Asked Questions
What rate limits should I set?
For login and password reset: 5 requests per minute per IP. For authenticated API endpoints: 60 requests per minute per user. For public forms (contact, registration): 3-5 per minute per IP. For general web pages: 120 per minute per IP. Adjust based on your legitimate traffic patterns.
Does rate limiting work behind a load balancer?
You need to configure the TrustedProxy middleware so Laravel gets the real client IP from X-Forwarded-For headers. Without this, all requests appear to come from the load balancer IP, and one user hitting the limit blocks everyone. Also use Redis as your cache driver so rate limits are shared across multiple servers.
Can attackers bypass rate limiting?
IP-based rate limiting can be bypassed with rotating proxies. For login protection, combine IP-based limits with account-based limits (lock the account after N failed attempts). For API abuse, use per-user token limits. Adding CAPTCHA after repeated failures provides an additional layer.
What cache driver should I use for rate limiting in production?
Use Redis. The default file-based cache driver does not share state across multiple application servers, meaning each server maintains its own count and an attacker can bypass a per-server rate limit by distributing requests across servers. Redis provides a centralized, fast store that all application instances share. Set CACHE_DRIVER=redis and RATE_LIMITER_DRIVER=redis in your .env.
How do I handle legitimate high-frequency API clients without blocking them?
Use per-user rate limits rather than IP-based limits for authenticated endpoints, then issue higher-tier API limits for clients that need them: RateLimiter::for('api', fn ($request) => $request->user() ? Limit::perMinute($request->user()->api_rate_limit) : Limit::perMinute(20)->by($request->ip())). Store the rate_limit on the user model and adjust it per client.
Related Security Terms
Related Guides
How to Prevent SQL Injection in Laravel
SQL injection vulnerabilities in raw queries and improper Eloquent usage can expose your database. Learn how to write secure queries.
Fix Missing CSRF Protection in Laravel: @csrf, VerifyCsrfToken & API Routes
Laravel forms without @csrf tokens are vulnerable to cross-site request forgery. Learn how to add CSRF protection, configure VerifyCsrfToken exceptions, and handle CSRF for API routes.
Fix CORS Misconfiguration in Laravel: Wildcard Origins, Credentials & config/cors.php
Using Access-Control-Allow-Origin: * with credentials enabled? That lets any site call your API as the logged-in user. Here is how to lock down config/cors.php properly.
Is your Laravel app exposed right now?
34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.