Laravel API Security: 7 Hardened Practices I Use in Production

90% of API breaches are preventable. Here are 7 Laravel API security practices I use in production with code and real stories.

Laravel API Security: 7 Hardened Practices I Use in Production

Laravel API Security: 7 Hardened Practices I Use in Production


1. Rate Limiting: Stop Brute Force Before It Starts

Client story: A SaaS login API was hit with 10K requests in 5 minutes. Laravel’s built-in throttle saved the server.

// routes/api.php
Route::middleware('throttle:60,1')->group(function () {
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/password/reset', [PasswordController::class, 'send']);
});
  • 60 requests per minute per IP
  • Customize per endpoint: throttle:10,1 for password reset
  • For API keys: use custom limiter
// App\Providers\RateLimiterServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(100)->by($request->header('X-API-KEY') ?? $request->ip());
});

2. Sanctum vs JWT: Choose Once, Sleep Well

Use CaseWinnerWhy
SPA + MobileSanctumCookie-based, CSRF-safe
Third-party appsJWT (Tymon)Stateless, scalable

My Rule: Use Sanctum unless you need stateless tokens.

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
// routes/api.php
Route::middleware('auth:sanctum')->get('/user', function () {
    return auth()->user();
});

Pro tip: Rotate tokens on password change

auth()->user()->tokens()->delete();

3. Bulletproof Validation with Form Requests

Never trust request()->all(). Use Form Requests.

php artisan make:request StoreUserRequest
class StoreUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email' => 'required|email|unique:users,email',
            'password' => 'required|min:8|confirmed',
            'role' => 'required|in:admin,editor,user',
            'ip' => 'ip'
        ];
    }

    protected function prepareForValidation()
    {
        $this->merge([
            'ip' => $this->ip(),
            'user_agent' => $this->header('User-Agent')
        ]);
    }
}

Controller stays clean:

public function store(StoreUserRequest $request)
{
    return User::create($request->validated());
}

4. API Keys + IP Whitelisting (B2B Gold)

For internal tools or partners:

// app/Http/Middleware/CheckApiKey.php
public function handle($request, Closure $next)
{
    $key = $request->header('X-API-KEY');
    $client = Client::where('api_key', $key)->first();

    if (!$client || !in_array($request->ip(), $client->allowed_ips)) {
        return response()->json(['error' => 'Unauthorized'], 401);
    }

    $request->merge(['client' => $client]);
    return $next($request);
}
// routes/api.php
Route::middleware('api.key')->group(function () {
    Route::get('/reports', [ReportController::class, 'index']);
});

5. Real-Time Monitoring with Laravel Telescope

Catch attacks as they happen.

composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Watch in /telescope:

  • Failed login attempts
  • Slow queries from suspicious IPs
  • 429 responses (rate limit hits)

My workflow:

  1. Telescope → Slack alert on >50 failed logins/hour
  2. Auto-block IP via Cloudflare API

6. CORS & HTTPS: Non-Negotiable

// config/cors.php
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['https://app.yourdomain.com'],
'allowed_headers' => ['*'],
'supports_credentials' => true,

Force HTTPS in production:

// App\Providers\AppServiceProvider.php
public function boot()
{
    if (app()->environment('production')) {
        URL::forceScheme('https');
    }
}

Bonus: Add HSTS header via middleware:

$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

7. Automated Security Scans in CI/CD

Never deploy vulnerable code.

# .github/workflows/security.yml
name: Security Audit
on: [push, pull_request]
jobs:
  phpstan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: composer install --no-progress
      - run: ./vendor/bin/phpstan analyse

  npm-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npm audit --audit-level=high

Add OWASP ZAP or Snyk for dependency scanning.


Final Checklist (Copy-Paste into Notion)

- [ ] Rate limiting on auth endpoints  
- [ ] Sanctum + token rotation  
- [ ] Form Requests for all inputs  
- [ ] API key + IP whitelist (B2B)  
- [ ] Telescope + Slack alerts  
- [ ] CORS locked to domain  
- [ ] HTTPS + HSTS enforced  
- [ ] CI/CD security scans