Home About Skills Products Work
Projects Services Experience
Learn
Tutorials Courses Blogs Resources
Contact
Blog · Security

API Rate Limiting and Authentication Best Practices

API Rate Limiting and Authentication Best Practices

An API with no rate limiting is one bad actor (or one buggy client retrying in a loop) away from taking down your entire service. Combined with solid authentication, rate limiting is one of the highest-leverage things you can add to any public-facing API.

Basic Rate Limiting in Laravel

Route::middleware('throttle:60,1')->group(function () {
    Route::apiResource('posts', PostController::class);
});

throttle:60,1 allows 60 requests per 1 minute, per authenticated user (or per IP for guests). Exceeding it returns a 429 Too Many Requests automatically — no custom code required.

Different Limits for Different Routes

// RouteServiceProvider
RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip()); // stricter — brute-force protection
});

A login endpoint deserves a much stricter limit than a general read endpoint — 5 attempts per minute meaningfully slows down credential-stuffing attacks without noticeably affecting a real user who occasionally mistypes their password.

Authentication: Choosing the Right Mechanism

// Sanctum — simple token auth for a first-party SPA or mobile app
$token = $user->createToken('mobile-app')->plainTextToken;

// Passport — full OAuth2, needed when THIRD-PARTY apps need to access your API
// on a user's behalf, with the user explicitly granting scoped permission

Most APIs need Sanctum, not a full OAuth2 server — Passport's added complexity only pays for itself when genuinely external applications need delegated access, not when you're just authenticating your own first-party clients.

Token Scopes: Limiting Blast Radius

$token = $user->createToken('read-only-integration', ['posts:read']);

// In a controller or middleware
if (!$request->user()->tokenCan('posts:write')) {
    abort(403);
}

A token issued for a read-only reporting integration shouldn't be capable of deleting data even if it leaks — scoped abilities are the mechanism that enforces that, rather than relying on "the integration would never try to do that."

Returning Useful Rate Limit Headers

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
Retry-After: 30

Laravel adds these automatically on throttled routes — a well-behaved client uses them to back off proactively rather than hammering the API and repeatedly hitting 429s.

Beyond the Basics: Per-Endpoint and Tiered Limits

A production API often needs more nuance than one global limit — a search endpoint hitting Elasticsearch might need a lower limit than a simple cached-lookup endpoint, and paid-tier customers might get a materially higher limit than free-tier ones. Laravel's RateLimiter::for() supports exactly this kind of per-route, per-user-tier configuration without custom middleware.

Web Application Security: A Practical OWASP Top 10 Guide

SQL injection, broken access control, XSS, mass assignment, and misconfiguration — the real vulnerabilities developers actually cause, with fixes.

Esc