Laravel ships with rate limiting support but doesn't configure it out of the box. This project wires up two layers in AppServiceProvider::boot().
Global (api) applies to every route at 60 requests per minute, keyed by authenticated user ID or IP for guests. This is the baseline protection against scraping and abuse.
Auth (auth) applies only to unauthenticated auth routes (login, register, forgot-password, reset-password) at 5 requests per minute, keyed by the submitted email combined with IP. The combo key means an attacker can't brute-force a single account from multiple IPs without hitting per-IP limits, and can't cycle through emails from one IP without hitting that limit either.
The global throttle is prepended to the api middleware stack in bootstrap/app.php, so it covers everything without touching individual routes. The auth throttle is a middleware group wrapping the four auth routes in routes/api.php.
The 429 response is customized in the exception handler (bootstrap/app.php) to use a lang string from responses.throttle instead of Laravel's default "Too Many Attempts." message. It preserves the Retry-After and X-RateLimit-* headers.
Laravel's password broker has built-in per-email throttling (default 60 seconds, configurable via passwords.users.throttle in config/auth.php). That prevents flooding a single inbox. The route-level auth throttle adds per-IP protection on top, stopping someone from probing thousands of different email addresses.
The values (60 and 5 per minute) are hardcoded in AppServiceProvider. They're not in config or .env because they rarely change and a dedicated config file for two integers isn't worth the indirection. If you need to adjust them, it's a one-line edit.
For production apps with higher traffic, you might bump the global limit or switch the cache driver to Redis for better performance under load. The rate limiter uses whatever CACHE_STORE is set to in .env.
Hit counts and decay timers live in the cache. Laravel's default cache driver is database, so they'll be in your cache table unless you've switched to Redis or another driver.