Laravel and Vue CORS errors

A same-origin Inertia app does not need CORS. A separate SPA does, and a 419 is a different failure from a blocked response.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/laravel-vue-cors.md
A same-origin Inertia app does not need CORS. A separate SPA does, and a 419 is a different failure from a blocked response.
Do not leave allowedorigins as [''] once credentials are on.
Do not copy this list and then invent extra routes.
Show the diff. Do not commit.

Next: Livewire or Inertia for a Laravel app

On this page · 8 sections
  1. When the browser sends a preflight
  2. The cookie settings that sit next to CORS
  3. 419 is not CORS
  4. Where this stops
  5. Recap
  6. FAQ
  7. Next steps
  8. Sources

The console says “blocked by CORS policy”, you add '*' everywhere, and login still fails. That’s because half of these errors are not CORS at all. Here is how to tell which bug you have, and the exact config for the one that is.

TL;DR

  • Same-origin Inertia apps never need CORS.
  • A separate SPA: list its exact origin, add login/logout to paths, set supports_credentials to true.
  • Never pair allowed_origins: ['*'] with credentials.
  • Failed preflight = CORS. 419 = CSRF/session. 401 = no session. Only the first is fixed in cors.php.

What you'll learn

  • When a Laravel + Vue app needs CORS at all
  • The config/cors.php values for a cookie-based SPA
  • How to tell CORS from a 419 or a 401

Who it's for: Developers running Vue on a different origin from Laravel

Applies to: Laravel 13 · Sanctum · Vue 3 · Axios

Most “Laravel Vue CORS” threads are two different bugs with the same console paragraph. One is a cross-origin SPA. The other is a CSRF failure that only looks like CORS because the browser hides the status. This page separates them.

Heads up: Inertia does not need this

The Inertia starter kit is same-origin. You open the Laravel URL, and @vite loads the dev assets. The browser never makes a cross-origin call, so config/cors.php is not the fix. If that app is what you are building, the cookie and login steps are in Sanctum for a Vue SPA only when the front end is a second origin. The architecture choice is Inertia, a SPA, or Nuxt.

When the browser sends a preflight#

A separate Vue app on http://localhost:5173 calling http://localhost:8000 is a different origin. The port counts. A POST with Content-Type: application/json, or with X-XSRF-TOKEN, triggers an OPTIONS preflight before the real request. If that OPTIONS response has no Access-Control-Allow-Origin for the Vue origin, the browser reports a CORS error and your Vue catch never sees the body.

Gotcha: '*' with credentials

Publish the config and turn credentials on. Sanctum’s cookie flow needs credentials. List the SPA origin. Do not leave allowed_origins as ['*'] once credentials are on. The browser rejects a literal Access-Control-Allow-Origin: * on a credentialed response, but Laravel’s CORS library does not send that star in this case. It reflects the request origin and then allows every caller.

bash
php artisan config:publish cors
config/cors.phpphp
'paths' => ['api/*', 'sanctum/csrf-cookie'], 
'paths' => ['api/*', 'sanctum/csrf-cookie', 'login', 'logout'], 
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:5173'],
'allowed_headers' => ['*'],
'supports_credentials' => true,

The default path list covers api/* and sanctum/csrf-cookie. It does not cover POST /login. Login lives on the web middleware group, which is the point of the Sanctum guide. If the preflight for /login never matches a path, the login request fails before Laravel runs the controller. Add each web path the SPA actually calls. Do not copy this list and then invent extra routes.

Tip: copy the origin from the browser

allowed_origins is the Vue origin, including the scheme and the port. http://localhost:5173 and http://127.0.0.1:5173 are not the same origin. Use the one the browser shows.

CORS only allows the browser to read the response. The session cookie still has to be sent. SANCTUM_STATEFUL_DOMAINS needs the SPA host and port, such as localhost:5173, and the Laravel host you open. supports_credentials on the CORS config is what lets the browser store and send that cookie.

Gotcha: a leading dot on localhost

Leave SESSION_DOMAIN empty on localhost. Set it to .example.com only when the SPA and the API are real subdomains. A leading dot on localhost drops the cookie. Axios still needs withCredentials and withXSRFToken. Those lines are in the Sanctum guide. This page does not repeat the login form.

419 is not CORS#

Open the failing request in the network panel.

What you seeWhat it isWhere to fix it
(failed), or “preflight blocked” in the consoleCORSOrigin, paths, supports_credentials
419CSRF cookie or session domainSanctum guide
401 from auth:sanctumNo sessionLog in again

If the status is (failed) or the console says the preflight was blocked, it is CORS. Check the origin, the path list, and supports_credentials.

If the status is 419, Laravel answered. The CSRF cookie was missing or the session domain was wrong. Fix that in the Sanctum guide. Changing allowed_origins will not turn a 419 into a 204.

A 401 from auth:sanctum means the request arrived and there is no session. That is also not CORS. Send the user back through GET /sanctum/csrf-cookie and POST /login.

Where this stops#

Nuxt on port 3000 is the same rule with a different origin. The call itself is Nuxt in front of the API. The JSON shape is API resources.

Recap#

  • Same-origin Inertia: no CORS config needed.
  • Separate SPA: exact origin, every web path it calls, credentials on.
  • SANCTUM_STATEFUL_DOMAINS and SESSION_DOMAIN sit next to CORS; set both.
  • Read the status code before touching cors.php.

FAQ#

Does a Laravel Inertia Vue app need CORS?

No. The page and the requests share one origin, so the browser never makes a cross-origin call.

Why does my Vue SPA login fail with a CORS error?

The default paths list does not include login. Add login and logout, list the SPA origin, and set supports_credentials to true.

Is a 419 a CORS error?

No. Laravel answered with a CSRF failure. Fetch /sanctum/csrf-cookie first and check SESSION_DOMAIN.

Are localhost and 127.0.0.1 the same origin?

No. Use the exact scheme, host and port the browser shows for the Vue app.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.