Reader Stacks

Enabling CORS in a Laravel API

A CORS misconfiguration only ever breaks browser-based cross-origin requests — a tool like Postman or a server-to-server call is entirely unaffected, since CORS is a browser-enforced restriction, not a server-side one.

CORS (Cross-Origin Resource Sharing) errors show up specifically when a browser-based frontend on one origin (like app.example.com) calls a Laravel API on a different origin (api.example.com) — Laravel ships with CORS support built in via a dedicated config file, not a package that needs separate installation on modern versions.

The CORS configuration file

// config/cors.php
return [
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://app.example.com'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => false,
];

Restricting which routes CORS applies to

'paths' => ['api/*', 'sanctum/csrf-cookie'],

paths restricts CORS handling to only the routes matching these patterns — typically just the API routes, since a same-origin traditional web page being served doesn't need CORS headers at all, and unnecessarily applying them site-wide is pointless overhead.

Specifying allowed origins explicitly

'allowed_origins' => ['https://app.example.com', 'https://admin.example.com'],

Explicitly listing each legitimate frontend origin allowed to call the API is the safer approach — the browser only allows the cross-origin request through if the responding Access-Control-Allow-Origin header matches the requesting page's actual origin.

The wildcard origin, and why it's usually the wrong choice for an authenticated API

'allowed_origins' => ['*'], // allows any origin

'*' allows literally any website to make cross-origin requests to the API — acceptable for a genuinely public, unauthenticated API (like open weather data), but it cannot be combined with supports_credentials: true at all (browsers actively block this combination) — meaning it's incompatible with cookie-based authentication like Sanctum's SPA authentication.

Enabling credentials (cookies) for a cross-origin request

'supports_credentials' => true,
'allowed_origins' => ['https://app.example.com'], // must be specific, not '*'
// On the frontend fetch call
fetch('https://api.example.com/user', {
    credentials: 'include'
});

supports_credentials: true is required for cookie-based cross-origin authentication (like Sanctum's SPA mode) to work at all — and it specifically requires an exact origin list rather than the * wildcard, a deliberate browser-level security restriction that Laravel's config can't override.

Understanding the preflight OPTIONS request

For anything beyond a "simple" request (a custom header, a non-GET/POST method, a JSON content type), the browser automatically sends a preliminary OPTIONS request first, checking whether the actual request is allowed before sending it — Laravel's CORS middleware handles responding to this preflight request automatically; no manual route or controller handling for OPTIONS is needed.

Why a CORS error is a browser-only restriction, not a server-side one

CORS is entirely enforced by the browser itself, not the server — a tool like Postman, curl, or a server-to-server request from another backend is completely unaffected by CORS configuration, since only an actual browser executing JavaScript from a web page enforces this restriction; this is precisely why "it works in Postman but not from my frontend" is a classic CORS symptom, not a sign of a broken endpoint.