New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jul 10, 2026 Edit on GitHub

Fixing It Properly

Here's the good news you earned in Phases 1 and 2: the fix is almost always a few response headers on the server, not the frontend. The browser is waiting for the server to send a permission slip - so we send it: a scannable cheat-card for when you're blocked right now, the proper fixes underneath, the credentials trap that catches everyone, and a dev-only proxy for when you can't change the server.

The cheat-card

Match the symptom to the row, then read the section under it. The fix is always on the server unless noted.

What you're seeing The calm fix
No 'Access-Control-Allow-Origin' header is present Server sends no CORS at all - add Access-Control-Allow-Origin for your origin (§1)
Header present but ...not equal to the supplied origin Server allows a different origin - set it to your origin, or reflect it (§1)
Response to preflight request doesn't pass access control check Add Access-Control-Allow-Methods and Access-Control-Allow-Headers and answer OPTIONS (§2)
Cookies/auth not sent, or "credentials" error with * You can't combine credentials with * - name the exact origin (§3)
Can't touch the server (third-party API, no access) Use a dev proxy so the browser sees one origin (§4)
Works in curl / Postman but not the browser That's expected - CORS is browser-only. Your headers are the fix (Phase 1)

1. Allow your origin (the everyday fix)

Most CORS errors are just a server that never sends Access-Control-Allow-Origin. You add it to the response. The cleanest value is the exact origin you want to allow:

HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://localhost:5173

What just happened: the server now names http://localhost:5173 as an allowed reader. The browser compares it to the Origin it sent, sees a match, and releases the response to your JavaScript. The error is gone.

If you need to allow several origins (say dev and staging), the server should keep a small allowlist and reflect the request's Origin back only when it's on that list:

This is the correct way to support multiple origins. Resist the urge to reach for * - the next sections explain why.

2. Make the preflight pass

If your error mentions preflight, the server is rejecting the OPTIONS "may I?" request (Phase 2). The server has to answer OPTIONS with the methods and headers your real request needs:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

What just happened: the server told the browser, ahead of time, that this origin may use those methods and send those headers. The browser approves the preflight and then fires your real request. Two common trip-ups: the server must actually handle the OPTIONS route (many frameworks need this enabled), and Access-Control-Allow-Headers must include every custom header you send - Authorization and a non-form Content-Type are the usual missing ones.

💡 Most web frameworks ship a CORS middleware (a setting or a small package) that sets all of these for you from one config block. Reach for that rather than writing headers by hand - but you now know exactly what it's doing under the covers, which is what makes it debuggable.

3. The credentials trap (read this before you ship)

📝 Credentials. In CORS, "credentials" means cookies, HTTP authentication, or a request sent with fetch(url, { credentials: "include" }). These are how a browser proves who the user is.

Here's the rule that catches everyone:

⚠️ You cannot combine Access-Control-Allow-Origin: * with credentials. If the request sends credentials, the server must name a single, specific origin - the wildcard is rejected by the browser. This is deliberate: * means "any site may read this," and "any site may read this with the user's cookies attached" would re-open the exact hole the same-origin policy exists to close.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173    ← MUST be a specific origin, never *
Access-Control-Allow-Credentials: true                ← required to let cookies/auth through

What just happened: the server allowed one named origin and set Access-Control-Allow-Credentials: true, so the browser will both send the user's cookies and let your JavaScript read the response. If the server had answered Access-Control-Allow-Origin: * here, the browser would block it with a credentials error no matter what else was set.

This is why the multi-origin allowlist from §1 matters: with credentials, reflecting the exact origin is not just tidy - it's the only thing that works.

The big gotcha: don't * your way out of it

⚠️ Access-Control-Allow-Origin: * is not a fix - it's a decision to let every website on the internet read that response. It's the first thing people paste in to make the red text go away, and for a truly public, non-credentialed, read-only endpoint (a public weather feed, say) it's fine. But on anything that returns user data, sits behind auth, or lives on a private network, * is a real security mistake:

  • It can't be used with credentials anyway (§3), so it often doesn't even solve your actual problem.
  • It tells every origin - including malicious ones - that their JavaScript may read your responses. For an internal or authenticated API, that's the door you were trying to keep shut.

The safe default is: name the specific origin(s) you actually trust. Use * only when you genuinely mean "this is public to the entire web, with no user-specific data."

4. The dev workaround: a proxy

Sometimes you can't change the server - it's a third-party API, or someone else owns it. In development, you can sidestep CORS entirely with a proxy: your own dev server forwards the request, so the browser only ever talks to one origin (yours).

What's happening: the browser thinks it's talking to its own origin (localhost:5173), so the same-origin policy is satisfied and no CORS headers are needed. The actual cross-origin call happens server-to-server, where CORS doesn't apply at all (remember Phase 1: CORS is browser-only). Most frontend dev servers have a built-in proxy setting for exactly this.

⚠️ A proxy is a dev convenience, not a production CORS fix. In production, the proper answer is still correct headers on the server you control. Don't ship a hack that papers over a header you could just set.

Recap

  1. The fix is server-side headers, not frontend code.
  2. Send Access-Control-Allow-Origin with your exact origin; for several origins, keep an allowlist and reflect the matching one.
  3. For preflights, also send Access-Control-Allow-Methods and Access-Control-Allow-Headers, and actually handle the OPTIONS request.
  4. Credentials + * is forbidden - name a specific origin and add Access-Control-Allow-Credentials: true.
  5. Don't reach for Access-Control-Allow-Origin: * on a credentialed or private API - it opens it to the whole web. A dev proxy is the right temporary workaround when you can't change the server.

You came here blocked; now you can read the error, point at the exact missing header, and fix it without quietly opening a hole. That's the whole skill. The deeper material - caching preflights with Access-Control-Max-Age, per-route policies, and CORS in front of CDNs and API gateways - is a follow-up guide for when you need it. For now, you're unblocked, safely.


← Phase 2: Reading the Error & the Headers · Guide overview

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. Where is a CORS error fixed?

2. Why can you not combine Access-Control-Allow-Origin: * with credentials?

3. A dev proxy fixes CORS by...