Skip to content

What Is CORS, and Should Just Any Website Be Able to Call My API?

6 min read

You saw Access-Control-Allow-Origin: * in your headers, or your API happily answered a request from a random site, and you're wondering if that's a hole. Short answer: an open CORS setting is usually fine, and it is not what keeps your data safe. CORS decides which websites a browser will let read your API's response. It does not decide who is allowed to call the API. Your real protection is authentication and access rules on the server, not CORS.

Why this happens

CORS (Cross-Origin Resource Sharing) is a browser rule. When code on someone-else.com tries to read a response from your-api.com, the browser checks your API's Access-Control-Allow-Origin header before handing the data back to that page's JavaScript. A wildcard (*) tells the browser "any origin may read this."

Your AI agent set * because it makes the error go away. During building you hit a "blocked by CORS policy" message, asked the agent to fix it, and the fastest fix that unblocks the demo is to allow everything. It worked, so nobody revisited it. The agent built what you asked (make the CORS error stop), not what you didn't ask (allow only my own site).

The important thing to understand: CORS is not a lock on your API. A wildcard does not "let hackers in." Anyone can already call your API directly with curl, Postman, or a script, because those aren't browsers and CORS never applies to them. CORS only governs what other people's web pages can read in a user's browser.

How to check

  1. Look at the response headers from your API. In a terminal:

    curl -I https://YOUR-API.com/api/your-route
    

    Find the Access-Control-Allow-Origin line. * means any site's page can read the response. A specific domain means only that site can.

  2. Decide which case you're in:

    • Public, non-sensitive data (a public listing, a health check, content anyone can already see): a wildcard is genuinely fine.
    • Anything tied to a logged-in user, or that uses cookies for auth: the origin should be restricted to your own domain.
  3. The dangerous combination to look for is a wildcard origin together with credentials. If you see both Access-Control-Allow-Origin: * (or an origin that reflects whatever site asked) and Access-Control-Allow-Credentials: true, that is a real misconfiguration worth fixing now.

The fix

  1. Set a specific allowed origin instead of *. Use your real front-end URL, for example https://app.yourdomain.com. On Express this is the origin option in the cors() middleware; on Next.js route handlers it's the Access-Control-Allow-Origin header you return; on Supabase Edge Functions and most hosts it's a header you set on the response.
  2. List each domain you actually use. Usually that's your production domain plus http://localhost:3000 for local development. Allow those, deny the rest.
  3. Never combine a reflected or wildcard origin with Access-Control-Allow-Credentials: true. If you send cookies, you must name an exact origin. Browsers reject * with credentials anyway, so a "clever" fix that echoes the request's origin back is the real danger here. Don't do that.
  4. Re-run the curl -I check and confirm the header now shows your domain, then load your app and make sure it still works.

The trap to avoid

Do not treat CORS as your security layer. Tightening CORS to your own domain is good hygiene, but it stops exactly one thing: another website reading your API in a victim's browser. It does nothing against a direct script that calls your API with a stolen or guessed request. If your API returns a user's data whenever the right ID is in the URL, locking down CORS changes nothing, because the attacker isn't using a browser. That is a server-side authorization problem, and it's the one that actually matters.

Where this fits

CORS confusion sends founders down the wrong path: they spend an afternoon perfecting origin headers while the API still hands out any user's records to a plain script. Getting beta-ready means fixing the real lock (auth and per-request access checks) and setting a sane origin as a tidy extra, not the other way round. The free Readiness Report tells you which of the two you're actually missing, and separates the cosmetic CORS warning from a genuine hole. If you'd rather have the fixes handed to you in order, that's what the Finishing Pass is for. It also pairs well with understanding what your public key can and can't do.