Fix CORS Misconfiguration in Laravel: Wildcard Origins, Credentials & config/cors.php

Using Access-Control-Allow-Origin: * with credentials enabled? That lets any site call your API as the logged-in user. Here is how to lock down config/cors.php properly.

High severity Application Security Updated 2026-03-01 Markdown

The same-origin policy is a foundational browser security rule: JavaScript on one origin (scheme + domain + port) cannot read responses from a different origin. This prevents a malicious site from using your browser's cookies to fetch your bank balance and send it to the attacker. CORS (Cross-Origin Resource Sharing) is a controlled relaxation of this rule, allowing servers to explicitly declare which origins may read their responses — essential for modern SPA and mobile application architectures.

CORS misconfiguration is consistently one of the most common API security issues, appearing in the OWASP API Security Top 10. The most dangerous form is a dynamically reflected origin: rather than specifying allowed origins in config/cors.php, some implementations read the Origin request header and echo it back in Access-Control-Allow-Origin. This effectively implements wildcard behavior while appearing to be a specific allowlist, allowing any domain to make cross-origin requests with credentials. An attacker's site becomes an implicitly 'allowed' origin, and their JavaScript can read the full authenticated API responses from your application.

Real-world CORS attacks have been used to steal OAuth tokens, read private user data, and perform account takeovers. Unlike many security misconfigurations, CORS issues can be difficult to detect because they only manifest in browser-context cross-origin requests, not in direct server-to-server API testing with curl. During development, wildcard CORS (CORS_ALLOWED_ORIGINS=*) is common for convenience, and these development configurations frequently reach production unchanged through deployment pipelines that don't enforce environment-specific values.

The Problem

A misconfigured CORS policy, especially using Access-Control-Allow-Origin: *, allows any website on the internet to make requests to your API on behalf of authenticated users. When combined with Access-Control-Allow-Credentials: true, attackers can read sensitive data and perform actions through a victim's browser session. This effectively bypasses same-origin policy protections.

How to Fix

  1. 1

    Configure allowed origins in config/cors.php

    Open config/cors.php and replace wildcard with specific origins:

    return [
        'paths' => ['api/*', 'sanctum/csrf-cookie'],
        'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
        'allowed_origins' => [
            'https://yourdomain.com',
            'https://app.yourdomain.com',
        ],
        'allowed_origins_patterns' => [],
        'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-TOKEN'],
        'exposed_headers' => [],
        'max_age' => 86400,
        'supports_credentials' => true,
    ];
    Never use '*' for allowed_origins when supports_credentials is true.
  2. 2

    Use environment-specific CORS origins

    Make CORS origins configurable per environment. In config/cors.php:

    'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', 'https://yourdomain.com')),

    Then in your .env files:

    # .env.local
    CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
    # .env.production
    CORS_ALLOWED_ORIGINS=https://yourdomain.com,https://app.yourdomain.com
  3. 3

    Restrict CORS to API routes only

    Limit CORS to only the routes that need cross-origin access. In config/cors.php:

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

    Do not use:

    'paths' => ['*'],
    This prevents CORS headers from being applied to your web routes, admin panels, and other sensitive endpoints that should never be accessed cross-origin.

How to Verify

Test your CORS configuration with curl:

curl -I -X OPTIONS https://yourdomain.com/api/endpoint \
  -H "Origin: https://evil-site.com" \
  -H "Access-Control-Request-Method: GET"

The response should NOT include Access-Control-Allow-Origin: https://evil-site.com. Only requests from your whitelisted origins should receive CORS headers.

Prevention

Review config/cors.php during code review for any pull request that modifies it. Never use wildcard origins in production. Document your CORS policy and the reason each allowed origin is included. Use StackShield to monitor your CORS headers and alert on misconfigurations.

Frequently Asked Questions

When is it okay to use wildcard CORS?

Wildcard Access-Control-Allow-Origin: * is acceptable only for truly public, read-only APIs that serve no authenticated or sensitive data. Public APIs like weather data or open datasets can safely use wildcards. If your API uses authentication of any kind, never use wildcards.

What is the difference between CORS and CSRF?

CORS controls which origins can read responses from your API via browser JavaScript. CSRF protects against forged form submissions. They complement each other: CORS prevents data theft via AJAX, while CSRF prevents action execution via forms. You need both.

Why do I get CORS errors in development but not production?

Development typically runs on different ports (localhost:3000 for frontend, localhost:8000 for Laravel), which are different origins. Add your development origins to the allowed list or use environment-specific CORS configuration as shown in step 2.

How do I allow multiple subdomains without listing each one?

Use the allowed_origins_patterns key in config/cors.php with a regular expression: 'allowed_origins_patterns' => ['#^https://[a-z0-9-]+\.yourdomain\.com$#']. This matches any subdomain of yourdomain.com while still rejecting external origins. Be careful with regex patterns — an overly broad pattern can accidentally allow unintended origins.

What are preflight OPTIONS requests and why do they matter?

Before sending a cross-origin request with non-simple methods or custom headers, browsers send an HTTP OPTIONS preflight request to check if the server allows it. Laravel's CORS middleware handles this automatically, but you must ensure your web server doesn't block OPTIONS requests before they reach PHP. If OPTIONS requests return 405 or are blocked by a firewall rule, CORS will fail for all non-simple requests.

Free security check

Is your Laravel app exposed right now?

34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 60 seconds.