Skip to main content
business and corporate websitesbusiness and corporate website packagesbusiness and corporate website designwebsite design business and corporatewebsite business and corporatebest business and corporate websitesbusiness and corporate website and seoannual website packagesprofessional website design mumbai

Laravel Security Guide: Supply-Chain Risks, Composer, and Application Scanning

Published: July 6, 2026
Written by Sumeet Shroff
Uncategorized
07.06.26
Laravel Security Guide: Supply-Chain Risks, Composer, and Application Scanning

Laravel API development is the fastest way to ship a reliable, secure backend for a SPA, mobile app, or third-party integration. Laravel 12 (released March 2025, PHP 8.2 minimum) provides tools like Sanctum for authentication, Eloquent Resources for response shaping, native CORS handling, and Redis-backed rate limiting. Correct integration is crucial. This guide covers every layer of a production-grade API: scaffolding, authentication, response design, versioning, rate limiting, queue workers, security, and testing. Before starting, review our Laravel security guide for securing your Laravel application at the infrastructure level.

Whether you're a solo developer in Mumbai building a client portal or an engineering team shipping a multi-tenant SaaS product, the patterns in this guide will help you avoid common production failures.

Need a production-ready Laravel API built for your Mumbai business? Our team has shipped APIs for SaaS products, ecommerce platforms, and enterprise portals — with full test coverage and security-first architecture.

Discuss Your Laravel Project

Prerequisites and Version Requirements

Laravel 12 requires PHP 8.2 or higher. PHP 8.3 and 8.4 are supported, but PHP 8.5 compatibility was incomplete as of early 2026 — verify before upgrading. Required PHP extensions include Ctype, cURL, DOM, Fileinfo, Filter, Hash, Mbstring, OpenSSL, PCRE, PDO, Session, Tokenizer, and XML. Laravel 11 still receives security patches, making it a valid choice for existing codebases.

Composer 2.x, a Redis instance (for rate limiting, queues, and caching in production), and a MySQL or PostgreSQL database are necessary. File-based drivers are suitable for local development but do not scale under concurrent API load.

  • Laravel 12.x — current stable (March 2025); bug fix support for 18 months, security fixes for 2 years
  • Laravel 11.x — still receiving security patches; minimum PHP 8.2
  • Laravel Passport — upgrade to 13.7.1+ if using 13.x (CVE-2026-39976 patched)
  • Laravel Livewire — upgrade to 3.6.4+ (CVE-2025-54068, CVSS 9.8)

Scaffolding Your API

Laravel API scaffolding in Laravel 11 and 12 is opt-in. A fresh laravel new install does not include routes/api.php by default. The most common mistake is defining API routes before running the scaffold command, leading to 404 errors.

Run this command first:

# Install API scaffolding — creates routes/api.php, installs Sanctum
php artisan install:api

To use Passport instead (OAuth2 server flows only)

php artisan install:api --passport

This command creates routes/api.php, publishes Sanctum's configuration, adds the throttle:api middleware to the API route group, and runs the Sanctum migrations. Do not skip it.

API Versioning From Day One

URL-based versioning (/api/v1/, /api/v2/) is the most widely adopted strategy. Header-based versioning (Accept header) keeps URLs cleaner but is harder to debug in browsers and harder to route in middleware. Use URL versioning unless your team has a strong reason otherwise.

Set up separate route files per version:

// routes/api.php — versioned route groups
use Illuminate\Support\Facades\Route;

Route::prefix('v1')->group(base_path('routes/api_v1.php')); Route::prefix('v2')->group(base_path('routes/api_v2.php'));

Keep separate controller namespaces — App\Http\Controllers\Api\V1 and App\Http\Controllers\Api\V2 — and separate Eloquent Resource classes per version. This lets V1 and V2 evolve independently without breaking existing consumers. Retrofitting versioning after launch is expensive; the cost of not versioning from day one is far higher than the minor overhead of setting it up now.

Authentication: Sanctum vs Passport

Laravel Sanctum handles 95% of API authentication needs. It supports SPA cookie-based authentication (HttpOnly cookies, CSRF protection) and API token authentication (simple opaque tokens stored in the database) without OAuth2 complexity. Sanctum is installed by php artisan install:api and requires no additional configuration for most projects.

Use Passport only when you are building a full OAuth2 authorization server — specifically when third-party applications need to request access on behalf of your users via authorization_code or client_credentials grant flows. Passport adds migrations, encryption key management, and client management overhead that is completely unnecessary for a first-party SPA or mobile app.

ScenarioSanctumPassport
Own SPA consuming your API✓ RecommendedOverkill
Own mobile app✓ RecommendedOverkill
Simple API tokens for integrations✓ RecommendedOverkill
Third-party OAuth2 authorization_code flowNot supported✓ Required
Machine-to-machine client_credentialsCan work with tokens✓ Native support

Protect API routes with Sanctum's middleware:

// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->group(function () { Route::apiResource('posts', PostController::class); Route::get('/user', fn (Request $r) => $r->user()); });

Token storage matters. For browser-based SPAs, use Sanctum's stateful (HttpOnly cookie) authentication — never store tokens in localStorage, because XSS attacks can exfiltrate them. For mobile apps and CLI clients, use Sanctum API tokens stored in the device's secure keychain.

Eloquent API Resources: Shape Every Response

Eloquent API Resources are the standard transformation layer between your models and JSON. Every API endpoint that returns model data should go through a Resource class. Raw Eloquent model output exposes your internal database column names, timestamps in inconsistent formats, and any sensitive fields that happen to be on the model.

Create a resource:

php artisan make:resource PostResource

Then define the transformation:

// app/Http/Resources/PostResource.php
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'title' => $this->title, 'author' => new UserResource($this->whenLoaded('author')), 'comments_count' => $this->whenCounted('comments'), 'created_at' => $this->created_at->toISOString(), ]; } }

Two methods are essential for production use:

  • whenLoaded('relationship') — includes the relationship only if it was already eager-loaded. Without this, accessing $this->author inside toArray() fires a lazy-load query for every item in a collection — classic N+1.
  • whenCounted('relationship') — includes a count only if withCount() was appended on the query. Same principle: no withCount, no extra query.

The $wrap property defaults to 'data'. If existing API consumers expect unwrapped responses, set public static $wrap = null or call ->withoutWrapping() on collection responses. Make this decision explicitly — changing it after consumers are built breaks their parsing.

Preventing N+1 Queries in Controllers

N+1 queries are the single most common performance killer in Laravel APIs. A collection of 50 posts that each lazily loads an author relationship fires 51 database queries instead of 2. Under production load, this degrades response times from tens of milliseconds to seconds.

The fix is always eager loading. In your controller:

// app/Http/Controllers/Api/V1/PostController.php
use App\Http\Resources\PostResource;
use App\Models\Post;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

public function index(): AnonymousResourceCollection { $posts = Post::with(['author', 'tags']) ->withCount('comments') ->paginate(20);

return PostResource::collection($posts);

}

In non-production environments, enforce eager loading at the model level to catch violations early:

// app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;

public function boot(): void { Model::preventLazyLoading(! app()->isProduction()); }

For a comprehensive look at query optimization patterns, including cursor pagination, raw queries for bulk reads, and index strategies, see our guide on Eloquent optimization.

The beyondcode/laravel-query-detector package is an alternative that throws exceptions or logs warnings for N+1 queries in local and staging environments. Use either approach — the key is that N+1 violations surface before they reach production.

Form Requests, Validation, and Mass Assignment Safety

When an XHR or API request triggers a validation failure, Laravel 12 automatically returns a 422 JSON response with all validation errors — no extra configuration needed. You can customize the error envelope by overriding failedValidation() in a FormRequest class:

// app/Http/Requests/StorePostRequest.php
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class StorePostRequest extends FormRequest { public function authorize(): bool { return true; }

public function rules(): array
{
    return [
        'title'   => ['required', 'string', 'max:255'],
        'content' => ['required', 'string'],
    ];
}

protected function failedValidation(Validator $validator)
{
    throw new HttpResponseException(
        response()->json([
            'success' => false,
            'errors'  => $validator->errors(),
        ], 422)
    );
}

}

Always pass $request->validated() to model create/update calls — never $request->all(). The difference: validated() returns only the fields declared in your rules() array; all() returns every field the client sends, including fields that could exploit mass assignment vulnerabilities.

// Correct — only validated fields pass to the model
public function store(StorePostRequest $request): PostResource
{
$post = Post::create($request->validated());
return new PostResource($post);
}

Never define protected $guarded = [] on models that handle user-controlled input from API controllers. Never call forceFill() or forceCreate() on request data. Define $fillable explicitly on every model.

Rate Limiting: Redis-Backed, Production-Safe

Rate limiting in Laravel 11 and 12 is configured in bootstrap/app.php (not RouteServiceProvider as in Laravel 10). The throttle:api middleware applies to all api.php routes by default after running php artisan install:api.

// bootstrap/app.php — custom rate limiter
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

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

This configuration gives authenticated users 120 requests per minute (keyed by user ID to prevent one user's rate limit from affecting others) and unauthenticated visitors only 30 (keyed by IP to slow credential stuffing).

Critical production requirement: use Redis as your cache driver. The file or database cache driver creates race conditions and disk I/O bottlenecks under concurrent API load. Rate limiting via the file driver on a high-traffic endpoint is effectively no rate limiting at all — multiple concurrent requests read the counter before any write commits.

CORS Configuration

Laravel has handled CORS natively since version 9.2 via Illuminate\Http\Middleware\HandleCors. Configure it in config/cors.php:

// config/cors.php — typical API CORS setup
return [
'paths'                => ['api/'],
'allowed_methods'      => [''],
'allowed_origins'      => [env('FRONTEND_URL', 'https://app.example.com')],
'allowed_headers'      => ['Content-Type', 'Authorization', 'X-Requested-With'],
'supports_credentials' => true,
'max_age'              => 86400,
];

Never set allowed_origins to '*' when supports_credentials is true. Browsers reject credentialed requests (cookies, Authorization headers) to wildcard origins — the combination is both a security misconfiguration and a functional bug. Always enumerate specific allowed origins from environment variables so they differ between staging and production.

Queue Workers and Laravel Horizon

Long-running operations in API handlers — sending emails, processing uploads, calling third-party APIs — must go on a queue. An API endpoint that blocks for 3 seconds while sending a transactional email will fail under concurrent load. Dispatch the job and return 202 Accepted immediately.

Laravel Horizon is the official Redis queue monitoring dashboard. Use named queues with dedicated worker pools:

# Run workers with memory and job limits to prevent drift
php artisan horizon

Or individual workers

php artisan queue:work redis --queue=critical,default,emails,imports
--max-jobs=500 --max-time=3600

The --max-jobs and --max-time flags are mandatory in production. PHP processes accumulate memory over thousands of iterations. Workers without these flags will eventually exhaust server memory — not immediately, but after days or weeks of running, at the worst possible moment.

Performance: Laravel Octane

Laravel Octane keeps the application bootstrapped in memory between requests, eliminating the bootstrap overhead of every PHP-FPM request cycle. April 2026 benchmarks show RoadRunner delivering approximately 2.1x the throughput of PHP-FPM on typical API workloads. FrankenPHP is recommended for containerised and cloud-native deployments because it ships as a single binary with no PHP extension required.

DriverThroughput vs PHP-FPMBest ForRequirement
RoadRunner~2.1xPure API throughputGo binary
FrankenPHP~1.8–2xContainer / KubernetesSingle binary, no extension
Swoole~1x–1.5xCoroutines / async I/OC extension required

Octane requires a careful application audit before enabling. Static properties, singletons, and open file handles can leak state between requests in long-running processes. Any singleton that holds per-request state must be re-bound in the Octane lifecycle hooks. The performance gains are real, but they require deliberate code review.

We build and deploy Laravel APIs with Octane, Horizon, Redis, and full CI/CD pipelines for Mumbai businesses. If you want a scalable API that's production-ready from day one, let's talk.

Explore Custom Web App Development

Security Considerations and Known CVEs

Laravel API security requires active maintenance — not just correct initial configuration. The following issues are active in the 2025–2026 timeframe:

  • CVE-2026-39976 (CVSS 7.1) — Laravel Passport 13.0.0–13.7.0: client_credentials JWT tokens set sub to the client ID. If a user's integer ID matches the client ID, the TokenGuard authenticates them as that user. Fix: upgrade to laravel/passport >= 13.7.1. Advisory: GHSA-349c-2h2f-mxf6.
  • CVE-2025-54068 (CVSS 9.8) — Laravel Livewire up to v3.6.3: unauthenticated RCE via unsafe component property hydration. Fix: upgrade to Livewire >= 3.6.4.
  • CISA KEV: CISA flagged a Laravel vulnerability with a patch deadline of April 3, 2026. Verify your Laravel version is current.

Beyond CVE patches, apply these rules universally:

  • Set APP_DEBUG=false and APP_ENV=production in all production .env files. Debug mode exposes full stack traces and environment variable values in JSON error responses.
  • Rotate APP_KEY before deploying to production. Changing it in production invalidates all existing sessions and signed links — do it before you have users.
  • Run composer audit regularly. It checks your installed packages against the PHP Security Advisories Database.
  • Validate file uploads by MIME type using mimes: or the file rule — not by file extension alone. Store uploads outside the public directory and serve via signed URLs.
  • Set tokens with explicit exp (expiry) claims. Tokens without expiry remain valid permanently, even after compromise.
  • Use HTTPS exclusively. Set HTTPS mode on Sanctum cookies in production ('secure' => true in config/sanctum.php). Configure HSTS headers at the web server level.

Testing and Verification

Every production API endpoint needs a feature test that exercises the full HTTP stack — not just unit tests on controllers. Laravel's testing helpers make this straightforward with Pest or PHPUnit:

// tests/Feature/Api/V1/PostTest.php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

test('authenticated user can create a post', function () { $user = User::factory()->create();

$response = $this->actingAs($user, 'sanctum')
    ->postJson('/api/v1/posts', [
        'title'   => 'Hello World',
        'content' => 'Body text here.',
    ]);

$response->assertStatus(201)
         ->assertJsonStructure(['data' => ['id', 'title']]);

});

test('unauthenticated request returns 401', function () { $this->postJson('/api/v1/posts', ['title' => 'Test']) ->assertStatus(401); });

test('validation failure returns 422 with errors', function () { $user = User::factory()->create();

$this->actingAs($user, 'sanctum')
     ->postJson('/api/v1/posts', [])
     ->assertStatus(422)
     ->assertJsonStructure(['errors' => ['title', 'content']]);

});

Run the suite before every deployment:

php artisan test --parallel

Use RefreshDatabase in feature tests to reset state between runs. Never test against a production database. On CI/CD, configure a separate testing database connection so migrations and seeding are isolated.

Common Mistakes That Break Production APIs

  1. Not running php artisan install:api in Laravel 11/12 — routes/api.php does not exist in fresh installs; routes silently 404.
  2. Using $request->all() in Model::create() — bypasses $fillable guards and opens mass assignment attacks.
  3. Leaving APP_DEBUG=true in production — full stack traces and environment variable values leak in JSON error responses.
  4. Not using whenLoaded() in API Resources — accessing $this->relationship inside toArray() triggers a lazy-load query per resource item.
  5. Rate limiting without Redis — the file or database cache driver creates race conditions under concurrent load.
  6. Hardcoding allowed_origins: '*' with supports_credentials: true — browsers reject this combination and it's a security misconfiguration.
  7. Not versioning from day one — retrofitting /api/v2/ after launch requires coordinating client updates and maintaining dual routing indefinitely.
  8. Storing Sanctum tokens in localStorage — XSS attacks can exfiltrate them; use HttpOnly cookies for browser clients.
  9. Omitting --max-jobs and --max-time on queue workers — PHP processes accumulate memory; workers without these flags eventually exhaust server RAM.
  10. Laravel Passport 13.0.0–13.7.0 with client_credentials — CVE-2026-39976 allows a machine token to authenticate as a real user. Upgrade to 13.7.1+.
  11. Returning HTML error pages to API clients — happens when the exception handler doesn't detect the Accept: application/json header. Wrap routes in appropriate middleware or use $request->expectsJson().
  12. Calling Model::unguard() globally — disables all mass assignment protection across the entire application.

Architecture Decisions Summary

Every Laravel API project requires conscious choices on these axes. The right answer depends on your scale, team, and deployment environment — but the tradeoffs are well-established:

DecisionOption AOption BVerdict
AuthenticationSanctumPassportSanctum for 95% of projects; Passport only for OAuth2 server
API versioningURL prefix (/v1/)Accept headerURL versioning — more debuggable and cache-friendly
Response shapingEloquent ResourcesRaw arraysAlways use Resources — conditional fields prevent N+1
Paginationpaginate()cursorPaginate()Cursor pagination for large datasets (OFFSET degrades at millions of rows)
PerformancePHP-FPMOctane (RoadRunner/FrankenPHP)Octane for API-heavy workloads; requires app audit first
QueuesRedis driverDatabase driverRedis always in production; database only for dev
Read-heavy endpointsEloquentRaw DB queriesRaw queries for bulk exports/reports (2–5x faster)

Frequently Asked Questions

What is the difference between Laravel Sanctum and Passport for API authentication?

Laravel Sanctum is a lightweight authentication package for first-party SPAs, mobile apps, and simple API tokens. It does not implement OAuth2 and does not require migrations for clients, encryption keys, or grant management. Laravel Passport is a full OAuth2 server implementation needed only when you are issuing tokens to third-party applications via authorization_code or client_credentials grant flows. For most Mumbai businesses building their own API backend, Sanctum is the correct and simpler choice — Passport adds significant operational overhead that is only justified for public-facing OAuth2 providers.

Why does routes/api.php not exist in my new Laravel 11 or 12 project?

Starting in Laravel 11, the routes/api.php file is no longer included in the default project scaffold. Laravel made this change to keep new projects lean — many projects do not need an API. To restore it, run php artisan install:api, which creates the file, installs Sanctum, publishes its configuration, and registers the throttle:api middleware on the API route group. This is a one-time command; run it immediately after creating a new project that will serve API consumers.

How do I prevent N+1 query problems in Laravel API responses?

N+1 queries occur when a collection of models lazily loads a relationship for each item individually, firing one query per item instead of a single eager-load query. The fix is to use Post::with(['author', 'tags'])->withCount('comments')->paginate(20) in your controller, and to use $this->whenLoaded('author') and $this->whenCounted('comments') inside your Eloquent Resource's toArray() method. Add Model::preventLazyLoading(!app()->isProduction()) in AppServiceProvider::boot() to catch violations in development before they reach production.

Why should rate limiting in Laravel use Redis and not the file driver?

The file cache driver stores rate limit counters in files on disk. Under concurrent API requests, multiple processes can read the counter simultaneously before any write commits, creating race conditions where the counter increments incorrectly and the limit is never enforced. Redis uses atomic operations (INCR, EXPIRE) that eliminate this race condition entirely. Redis also handles the I/O throughput of hundreds of concurrent requests without the disk bottleneck that file-based storage creates. In production, always set CACHE_DRIVER=redis in your .env before enabling rate limiting.

What is the best strategy for versioning a Laravel REST API?

URL-based versioning using route prefix groups (/api/v1/, /api/v2/) is the most widely adopted and debuggable strategy for Laravel REST APIs. Keep separate controller namespaces (App\Http\Controllers\Api\V1) and separate Eloquent Resource classes per version so each version can evolve independently without breaking existing consumers. Start versioning from day one — retrofitting versioning after launch requires coordinating client updates across mobile apps, SPAs, and third-party integrations simultaneously, which is a costly and error-prone process. Header-based versioning (Accept header) is an alternative but is harder to test in browsers and harder to route in Laravel middleware.

Sumeet Shroff
Sumeet Shroff
Sumeet Shroff is the founder of Mumbai Web Designer, a full-service web design and development company based in Andheri West, Mumbai. Over more than a decade building websites for Indian businesses, he has led design and development across Next.js, Laravel, WordPress, and Shopify — the same stack the studio uses today to build fast, lead-generating sites for clients across Mumbai, Andheri, Bandra, Powai, Juhu, and Navi Mumbai. Sumeet's focus is practical rather than decorative: websites that load quickly, rank on Google, and turn visitors into enquiries. His work spans custom website design, ecommerce development, SEO, landing pages, and conversion rate optimisation, and he writes regularly on web design costs, platform choices, and the technical decisions that actually move business results. He founded Mumbai Web Designer to give local businesses an agency partner that stays accountable after launch — with clear contracts, open technology stacks clients fully own, on-page SEO built in from day one, and ongoing maintenance rather than a disappearing act once final payment clears. When advising a business owner, his first questions are always about the goal — leads, sales, bookings, credibility — before a single word about colours or layouts. Sumeet specialises in Next.js, Laravel, WordPress, Shopify, SEO, and UI/UX, and leads the team behind mumbaiwebdesigner.com. Connect with him on LinkedIn to talk web design, development, or SEO for Mumbai businesses.

Comments

Leave a Comment

Loading comments...