API Rate Limiting and Authentication Best Practices
Laravel rate limiting per route, Sanctum vs Passport, token scopes, and returning useful rate-limit headers to well-behaved clients.
The OWASP Top 10 is the industry-standard list of the most critical web application security risks — not theoretical, but based on real, widespread vulnerability data. Here's a practical guide to the ones every web developer will actually encounter.
// NEVER do this
$user = DB::select("SELECT * FROM users WHERE email = '{$email}'");
// Always use parameter binding
$user = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
Laravel's query builder and Eloquent parameter-bind automatically — the risk almost entirely reappears the moment a developer drops to raw string-concatenated SQL, which is exactly why that pattern should be treated as a hard rule violation in code review.
// Vulnerable: any logged-in user can view ANY invoice by guessing the ID
Route::get('/invoices/{id}', fn ($id) => Invoice::find($id));
// Fixed: verify ownership explicitly
Route::get('/invoices/{invoice}', function (Invoice $invoice) {
abort_unless($invoice->user_id === auth()->id(), 403);
return $invoice;
});
This — an authenticated user accessing another user's data just by changing an ID in the URL — is consistently the single most common real-world vulnerability across web applications, and the fix is always the same: verify ownership on every single access, never assume authentication alone is authorization.
{{-- Laravel Blade escapes by default — safe --}}
{{ $userComment }}
{{-- {!! !!} renders raw HTML — only ever use this for content you trust --}}
{!! $userComment !!}
The vast majority of real XSS vulnerabilities in Laravel apps come from developers reaching for {!! !!} to render user-supplied content, defeating the framework's default protection specifically where it mattered most.
// Vulnerable: a malicious client adds "is_admin": true to the request body
$user->update($request->all());
// Fixed: only ever update fields you explicitly validated
$user->update($request->validate(['name' => 'required', 'email' => 'required|email']));
Laravel's $fillable array on a model is a second layer of defense against this exact attack, but validating and whitelisting the request itself remains the primary control.
APP_DEBUG=false in production — a debug page can leak stack traces, file paths, even environment variables.env to version controlContent-Security-Policy, X-Frame-Options, Strict-Transport-SecurityMost of these aren't exotic attacks requiring deep security expertise — they're basic hygiene mistakes: raw SQL, missing ownership checks, unescaped output, and unrestricted mass assignment. A framework like Laravel already defends against most of these by default; the actual risk is almost always a developer explicitly opting out of that default protection somewhere.
Laravel rate limiting per route, Sanctum vs Passport, token scopes, and returning useful rate-limit headers to well-behaved clients.