What Is CORS (Cross-Origin Resource Sharing)?
A browser security mechanism that controls which domains can make requests to your web application. By default, browsers block cross-origin requests (requests from a different domain). CORS headers tell the browser which origins, methods, and headers are allowed.
How It Works
CORS works through a browser-enforced header negotiation protocol that controls whether JavaScript on one origin can read responses from another origin.
Same-origin policy baseline — By default, browsers block JavaScript from reading responses to cross-origin requests. A script at app.com cannot read the response from api.com without explicit permission. This blocks a category of attacks where malicious JavaScript on evil.com would read data from APIs using the victim's session cookies.
Simple request flow — For simple requests (GET, POST with standard Content-Type, no custom headers), the browser sends the request normally with an Origin: https://app.com header. The server returns the response plus Access-Control-Allow-Origin: https://app.com. If the requesting origin matches the allowed origin, the browser permits JavaScript to read the response. If it doesn't match (or the header is absent), the browser blocks the response from being read by JavaScript — but the request WAS sent and processed by the server.
Preflight request flow — For non-simple requests (DELETE, PUT, or any request with custom headers like Authorization), the browser first sends a preflight: OPTIONS /api/resource HTTP/1.1 with Access-Control-Request-Method: DELETE and Access-Control-Request-Headers: Authorization. The server responds with Access-Control-Allow-Methods: GET, POST, DELETE and Access-Control-Allow-Headers: Authorization. Only if the preflight is approved does the browser send the actual request.
Credentials — credentials: 'include' in a fetch request tells the browser to send cookies cross-origin. The server must respond with Access-Control-Allow-Credentials: true AND a specific (non-wildcard) Access-Control-Allow-Origin for the browser to share the response with JavaScript. Wildcard origin + credentials is explicitly prohibited by the CORS specification — the browser ignores both.
Misconfiguration risks — The most dangerous CORS misconfiguration is reflecting the Origin header back in Access-Control-Allow-Origin combined with Allow-Credentials: true — this allows any origin to make credentialed requests and read responses.
Types of CORS (Cross-Origin Resource Sharing)
Wildcard Origin Misconfiguration
API returns `Access-Control-Allow-Origin: *` on endpoints that return user-specific data — any website can make unauthenticated requests and read the response.
# Check for wildcard CORS on authenticated endpoint:
curl -H "Origin: https://evil.com" -sI https://yourapp.com/api/user
# Dangerous response:
# Access-Control-Allow-Origin: *
# (Any site can read user data via JavaScript)
Origin Reflection Attack
Server reflects any Origin header back in `Access-Control-Allow-Origin` combined with `Allow-Credentials: true` — allows any site to make credentialed requests and read authenticated responses.
// Vulnerable: reflecting Origin header
$allowedOrigin = $request->header('Origin');
return $response->header('Access-Control-Allow-Origin', $allowedOrigin)
->header('Access-Control-Allow-Credentials', 'true');
// Attacker at evil.com can now read authenticated API responses
Null Origin Bypass
Server whitelist includes `null` origin — browsers send `Origin: null` from local files (file://) and some sandbox contexts, allowing local exploitation.
// Vulnerable: null origin whitelisted
'allowed_origins' => ['null', 'https://yourapp.com'],
// attacker.html opened from file:// sends Origin: null — allowed
In Laravel Applications
Configure CORS in config/cors.php. The most dangerous misconfiguration is setting allowed_origins to ["*"] (any domain) on authenticated endpoints. This allows any website to make requests to your API on behalf of your users.
Code Examples
config/cors.php: Permissive vs. Restrictive
Vulnerable
// config/cors.php — VULNERABLE: wildcard origin allows any site to read API
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'], // ANY origin can read responses
'supports_credentials' => true, // Sends session cookies cross-origin
// Note: browsers block wildcard + credentials per spec
// But wildcard alone still leaks non-authenticated responses
];
Secure
// config/cors.php — SECURE: explicit frontend origins only
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
'allowed_origins' => [
env('FRONTEND_URL', 'https://app.yourapp.com'),
],
'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
// .env:
// FRONTEND_URL=https://app.yourapp.com
// SANCTUM_STATEFUL_DOMAINS=app.yourapp.com
Real-World Example
With Access-Control-Allow-Origin: *, a malicious website can make AJAX requests to your Laravel API endpoints, reading data and performing actions as the authenticated user.
Why It Matters
CORS is one of the most misunderstood security mechanisms in Laravel development. The browser's same-origin policy is a security feature — CORS is the mechanism that grants controlled exceptions to that policy. When CORS is misconfigured to be overly permissive, it creates a vulnerability; when it is configured too strictly, it breaks legitimate cross-origin use cases.
The most critical CORS misconfiguration is allowed_origins: ['*'] combined with supports_credentials: true in config/cors.php. Credentials (cookies and Authorization headers) cannot be shared with wildcard origins per the CORS specification — but allowed_origins: ['*'] without credentials still allows any origin to read your API responses, which may expose sensitive data.
For Laravel Sanctum-based SPAs, the correct CORS configuration is specific: the frontend domain in allowed_origins, supports_credentials: true, and the SANCTUM_STATEFUL_DOMAINS environment variable set correctly. This is a common source of bugs that developers fix by using '*' — which breaks security.
Laravel API responses that return user-specific data, authentication tokens, or business logic results should always have strict CORS origins. Only the frontend domains that legitimately need access should be listed in allowed_origins. Review this list whenever you add a new frontend application or subdomain.
Common Misconceptions
Myth: CORS is a server-side security mechanism that prevents cross-origin attacks.
Reality: CORS is enforced by browsers, not servers. A direct HTTP request (curl, Postman, server-to-server) is never subject to CORS restrictions. CORS only restricts browser-based JavaScript from reading cross-origin responses.
Myth: Setting `Access-Control-Allow-Origin: *` is safe for public API endpoints.
Reality: Wildcard CORS on API endpoints that reflect user-specific data, even in aggregate, can leak information to any website. Use wildcard CORS only for truly public, non-authenticated data.
Myth: CORS prevents CSRF attacks.
Reality: CORS and CSRF are completely different protections. CORS controls which origins can read cross-origin responses. CSRF protects against cross-origin requests being submitted with credentials. A CORS misconfiguration does not introduce CSRF risk, and CSRF protection does not mitigate CORS issues.
How to Detect This
Review `config/cors.php` and check the `allowed_origins` array — any wildcard (`*`) entry should be justified and documented. Use `curl -H "Origin: https://evil.com" -I https://yourdomain.com/api/user` and check if the response includes `Access-Control-Allow-Origin: https://evil.com` or `Access-Control-Allow-Origin: *` — either indicates CORS is allowing unknown origins. Check for `Access-Control-Allow-Credentials: true` alongside a broad origin header — this combination is particularly dangerous. StackShield checks CORS headers on API responses for wildcard configurations and flags `Access-Control-Allow-Origin: *` on endpoints that return non-public data.
How to Prevent This in Laravel
-
1
Set `allowed_origins` in `config/cors.php` to an explicit list of frontend domains (never `['*']`) — configure these as environment variables so staging and production use different values.
-
2
Never combine `allowed_origins: ['*']` with `supports_credentials: true` — this is a CORS misconfiguration. Use specific origins when credentials are needed.
-
3
For Laravel Sanctum SPA authentication, set `SANCTUM_STATEFUL_DOMAINS=yourspa.com` in `.env` and verify it matches your frontend's domain exactly, including port for non-standard ports.
-
4
Test your CORS configuration with `curl -H "Origin: https://evil.com" -sI https://yourapp.com/api/user` — if the response includes `Access-Control-Allow-Origin: https://evil.com` or `*`, the configuration is too permissive.
-
5
Run `php artisan config:clear` after any changes to `config/cors.php` or `.env` CORS variables, as Laravel caches configuration and stale values may persist.
Frequently Asked Questions
How do I configure CORS for a Laravel API consumed by a React or Vue frontend?
Set `allowed_origins` in `config/cors.php` to your frontend's exact origin (e.g., `['https://app.yourapp.com']`), set `supports_credentials: true` if using session-based auth (Sanctum SPA mode), and set `SANCTUM_STATEFUL_DOMAINS=app.yourapp.com` in `.env`. On the frontend, configure `axios.defaults.withCredentials = true` or `credentials: 'include'` in fetch. During development, your frontend origin is typically `http://localhost:3000` — add it to `allowed_origins` in your local `.env`.
Does CORS prevent CSRF attacks?
CORS and CSRF protect against different things. CORS controls whether JavaScript on another origin can READ cross-origin responses. CSRF protects against cross-origin requests being SUBMITTED with session cookies, regardless of whether the response is readable. A CORS misconfiguration can allow reading sensitive data but does not create CSRF risk. A missing CSRF token allows unauthorized actions but does not affect CORS. They are independent mechanisms that must both be correctly configured.
What is the difference between `allowed_origins` and `allowed_origins_patterns` in Laravel CORS config?
`allowed_origins` is an exact match list — only origins that appear literally in the array are allowed. `allowed_origins_patterns` supports regex patterns for matching multiple subdomains: `['https://*.yourapp.com']` would match any subdomain. Be careful with patterns — overly broad patterns like `['https://yourapp.*']` could match `https://yourapp.evil.com` (a domain registered by an attacker). Use exact origins when possible and patterns only when necessary.
How do I debug CORS errors in a Laravel application?
Open browser DevTools → Console and Network tabs. CORS errors appear in the Console as "Access to fetch blocked by CORS policy." In the Network tab, check whether the preflight OPTIONS request returned the correct `Access-Control-Allow-Origin` header matching your frontend's origin. On the server side, run `curl -H "Origin: http://localhost:3000" -X OPTIONS -I https://yourapp.com/api/user` to see exactly what CORS headers the server returns. Check that `config/cors.php` is not cached with stale values by running `php artisan config:clear`.
Related Terms
Security Headers
HTTP response headers that instruct browsers how to handle your website's content securely. They protect against common attacks like cross-site scripting (XSS), clickjacking, and protocol downgrade attacks by telling the browser what actions are allowed.
Cross-Site Request Forgery (CSRF)
An attack where a malicious website tricks a user's browser into performing an unwanted action on a site where the user is authenticated. The browser automatically sends cookies with the request, so the target site processes it as a legitimate action from the user.
Monitor Your Laravel Application's Security
StackShield continuously checks your Laravel application from the outside, catching security issues before attackers find them.
Start Free Trial