API security is not optional. A single misconfigured endpoint can expose your entire user database, allow an attacker to impersonate any user, or enable denial-of-service attacks that take your business offline. We conduct security audits on Laravel APIs as part of every enterprise project handover. Here is the complete 10-point checklist we run through every time.
Use Laravel Sanctum for SPA authentication and simple mobile app token auth. Sanctum is lightweight, actively maintained, and sufficient for the vast majority of applications. Use Laravel Passport only if you need a full OAuth2 server — i.e., you are issuing tokens to third-party clients via authorisation code flow.
Common mistake: storing Sanctum tokens in localStorage. Always store them in secure, HttpOnly cookies (for SPAs) or in device-level secure storage (for mobile apps). localStorage is accessible from JavaScript and is vulnerable to XSS attacks.
Authentication answers "who are you?" — authorization answers "what are you allowed to do?" They are different problems. Every API endpoint that accesses a resource must verify that the authenticated user is authorised to access that specific resource.
// WRONG: Only checks authentication, not authorization
public function show(Invoice $invoice) {
return $invoice; // Any logged-in user can see any invoice!
}
// RIGHT: Uses a Policy to check ownership
public function show(Invoice $invoice) {
$this->authorize('view', $invoice);
return $invoice;
}
The missing authorization check (IDOR — Insecure Direct Object Reference) is the most common API vulnerability we find in audits. It's in the OWASP API Top 10 as the #1 API security risk.
Rate limiting
protects against brute-force attacks, credential stuffing, and scraping. In Laravel, apply rate
limiting with named limiters in RouteServiceProvider
and use the throttle
middleware on your API routes. Apply stricter limits to sensitive endpoints — authentication routes
should be significantly more restricted than read-only data endpoints.
RateLimiter::for('auth', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
RateLimiter::for('api', function (Request $request) {
return $request->user()
? Limit::perMinute(120)->by($request->user()->id)
: Limit::perMinute(30)->by($request->ip());
});
Never trust
user input. Use Laravel Form Request classes for all API input validation. Validate at the
boundaries — before any business logic executes. Specify allowed fields explicitly (use $request->validated()
not $request->all())
to prevent mass assignment vulnerabilities.
Eloquent's query builder uses PDO parameterized queries by default, which prevents SQL injection as long as you use it correctly. The danger is raw queries:
// VULNERABLE to SQL injection
DB::select("SELECT * FROM users WHERE name = '{$name}'");
// SAFE: Use parameterised bindings
DB::select('SELECT * FROM users WHERE name = ?', [$name]);
// Even better: Use Eloquent
User::where('name', $name)->get();
Never set
allowed_origins: ['*']
in production. Specify the exact domains of your frontend application. In config/cors.php,
set allowed_origins
to your exact frontend URLs. If you need to support multiple environments (staging, production),
manage this through environment variables.
If using JWTs, enforce these settings: sign with RS256 (asymmetric), not HS256; set short expiry times (15 minutes for access tokens); implement refresh token rotation; store refresh tokens server-side for revocation capability; transmit tokens via HttpOnly cookies, not request headers (prevents XSS theft). Never log JWT tokens — they are equivalent to passwords.
Set these headers on every API response via middleware:
$response->headers->set('Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy',
'strict-origin-when-cross-origin');
$response->headers->remove('X-Powered-By'); // hide PHP version
$response->headers->remove('Server'); // hide server info
Encrypt
sensitive data at rest using Laravel's built-in encryption (AES-256-CBC via the Encrypted
cast). Hash passwords with Bcrypt — never store them in plain text or use MD5/SHA1. Use $hidden
arrays on Eloquent models to prevent sensitive fields being serialised into API responses. Log what
users do but never log what their credentials are.
Your
application is only as secure as its dependencies. Run composer audit
as part of your CI pipeline — it checks all dependencies against the PHP Security Advisories
database. Automate this with a GitHub Actions step that fails the build if any high-severity
vulnerabilities are found. Keep Laravel itself and all packages updated, especially security
releases. Subscribe to Laravel security announcements via the official blog.
We run this 10-point security checklist on every client project. Let us audit your API and give you a detailed vulnerability report.
Request a Security Audit →.jpeg)


The same expertise behind these articles goes into every project we build.