Learn · Topic explainer
CORS explained: how cross-origin requests actually work
Cross-Origin Resource Sharing (CORS) is the browser mechanism that decides whether a fetch from `app.example.com` to `api.example.com` is allowed to read the response. It's not a security feature against attackers — it's a relaxation of the older same-origin policy that prevents a website you visit from reading data on another website you're logged into. This explainer covers what same-origin actually means, what preflight requests do, and the CORS configurations that look permissive but quietly break the security model.
The same-origin policy CORS relaxes
The same-origin policy is one of the oldest browser security rules: a script running on `https://attacker.com` cannot read responses from `https://yourbank.com`, even if the user is logged into both. Without it, any site you visit could pull your private data from any other site. "Same origin" means same scheme, same hostname, and same port. Different ports — even different subdomains — are different origins. CORS lets a server explicitly opt in to relaxing this rule for specific cross-origin clients.
Simple requests vs preflighted requests
Some cross-origin requests skip preflight. A `GET` or `POST` with only safe headers (Content-Type form-encoded, plain text, or multipart/form-data) goes straight to the server, and the browser checks `Access-Control-Allow-Origin` on the response. Anything else — a custom header like `Authorization`, a `Content-Type: application/json`, or a method like `PUT`/`DELETE` — triggers a preflight. The browser sends an `OPTIONS` request asking the server "are you OK with this?" and waits for the response before sending the real request. If the preflight fails, the real request never happens.
The headers that matter
Server response headers tell the browser what's allowed. `Access-Control-Allow-Origin` lists permitted origins (or `*` for any). `Access-Control-Allow-Methods` lists permitted HTTP methods. `Access-Control-Allow-Headers` lists permitted request headers. `Access-Control-Allow-Credentials: true` says "send cookies / Authorization with this request" — and crucially, requires `Access-Control-Allow-Origin` to be a specific origin, not `*`. `Access-Control-Max-Age` caches the preflight for N seconds so you don't preflight every request.
Two patterns that break the model
(1) `Access-Control-Allow-Origin: *` plus `Access-Control-Allow-Credentials: true` — browsers reject this combination, but some servers return it dynamically ("reflect the Origin header back, with credentials") which is functionally the same thing and effectively disables CORS protections. If your API needs credentials, allowlist specific origins. (2) Reflecting `Origin` blindly — many servers see the `Origin` header on a request and echo it back as `Access-Control-Allow-Origin`. That means any origin works, including malicious ones. Always validate `Origin` against an allowlist before reflecting.
When CORS isn't the answer
CORS protects browser fetches. It does nothing for server-to-server requests, mobile apps, or scripts running outside a browser. If your API needs to be safe from non-browser clients, CORS won't save you — you need authentication, rate limiting, and authorization checks on the server. Use CORS to enable legitimate cross-origin clients; use other layers for actual security against direct API abuse.
Related Scorifya checks
Try the focused tools
Single-purpose checkers that test exactly what this topic covers.
See how your site scores
Run a free Scorifya scan on any URL you're allowed to test. The score breaks down across TLS, security headers, exposure, cookies, and DNS — exactly the areas this explainer covers.