CCelestiallines.com
← Back to blog

How to Fix CORS Errors in a Laravel API

By Pawan Singh · Jun 2026

A CORS error is your browser protecting the user, not your server rejecting the request — understanding that distinction is most of the way to fixing it correctly instead of just disabling security checks until the error disappears.

What's Actually Happening

When a frontend on one origin (e.g. https://app.example.com) calls an API on a different origin (https://api.example.com), the browser enforces the Same-Origin Policy by default. CORS (Cross-Origin Resource Sharing) is the mechanism that lets a server explicitly opt certain origins back in via response headers. Critically: the request usually does reach your Laravel app and run successfully — the browser just refuses to hand the response back to the JavaScript that requested it, because the response didn't include permission headers for that origin. This is why a request can show as "successful" in your Laravel logs while the browser console shows a CORS error.

Configuring config/cors.php

Laravel ships CORS handling via the fruitcake/laravel-cors functionality built into the framework (or the standalone package on older versions), configured in config/cors.php:

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://app.example.com'],
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
];

Each field matters more than it looks:

  • paths — only requests matching these path patterns get CORS headers at all. Forgetting to include a route prefix here (a custom API path outside api/*) is a common cause of "I configured CORS and it's still not working."
  • allowed_origins — must match the frontend's exact origin, protocol included. http://localhost:3000 and https://localhost:3000 are different origins as far as CORS is concerned, and so are a bare domain and its www. subdomain.
  • supports_credentials — must be true if the frontend sends cookies (session-based auth, including Sanctum's SPA cookie authentication) with requests. When this is true, allowed_origins can not be a wildcard * — browsers reject the combination of wildcard origin plus credentials as a security measure, so you must list explicit origins.

The Preflight Request

For anything beyond a simple GET (a POST with a JSON body, a custom header, PUT/DELETE/PATCH), the browser first sends an automatic OPTIONS preflight request asking permission before sending the real one. If your Laravel app doesn't respond correctly to OPTIONS with the right CORS headers — often because a middleware or auth guard intercepts it and returns a 401/403 before CORS handling runs — the real request never gets sent at all, and the browser reports it purely as a CORS failure with no other detail.

Sanctum SPA Authentication Specifically

If you're using Sanctum's cookie-based SPA authentication (not bearer tokens), there's a second, separate configuration point beyond cors.php: SANCTUM_STATEFUL_DOMAINS in .env must list the frontend's domain, and the frontend must call /sanctum/csrf-cookie before its first authenticated request. Missing either of these produces symptoms that look identical to a plain CORS misconfiguration, which is why Sanctum-based setups are disproportionately represented in "I fixed CORS but it's still broken" reports.

Debugging Systematically

  1. Open the browser's Network tab, not just the console error — find the actual failed request and check whether it's the OPTIONS preflight or the real request that's failing.
  2. Check the response headers on that request for Access-Control-Allow-Origin — if it's missing entirely, Laravel's CORS middleware isn't running for that path (check paths in config/cors.php).
  3. If the header is present but doesn't match your frontend's exact origin, fix allowed_origins.
  4. If preflight succeeds (200) but the real request still fails, the problem has moved from CORS to something else — usually authentication (missing/expired Sanctum cookie) or an unrelated server error.

Common Issues

  • Works in Postman, fails in the browser — expected. Postman doesn't enforce CORS at all; only browsers do. This is not evidence the API itself is broken.
  • CORS error only on POST/PUT/DELETE, GET works fine — a failing or blocked preflight OPTIONS request; check middleware ordering.
  • Wildcard origin with credentials — browsers reject this combination outright; list explicit origins instead of * once supports_credentials is true.
  • Works locally, fails in productionallowed_origins still pointing at a local dev URL rather than the production frontend domain.

Need a decoupled frontend and Laravel API talking to each other cleanly? Get in touch — I work as a freelance Laravel developer.

Comments