API Security CORS
Cross-Origin Resource Sharing, called CORS, controls which websites may call your API from a browser. Browsers enforce a rule named the same-origin policy. A page loaded from one origin cannot read responses from a different origin unless the API grants permission. CORS is the permission system.
Layman's Explanation
Think of an apartment building with a doorman. Residents of Building A walk in freely. A visitor from Building B must appear on the guest list the doorman holds. CORS works as that guest list. The API publishes the list, and the browser plays doorman.
What Counts as an Origin
An origin combines three parts: protocol, domain, and port. Any change in one part creates a different origin.
| Page Origin | API Origin | Same Origin? |
|---|---|---|
| https://shop.example.com | https://shop.example.com | Yes |
| https://shop.example.com | http://shop.example.com | No (protocol) |
| https://shop.example.com | https://api.example.com | No (domain) |
| https://shop.example.com | https://shop.example.com:8443 | No (port) |
How a CORS Request Flows
Simple Requests
The browser sends the call and attaches an Origin header. The API replies with an Access-Control-Allow-Origin header. The browser compares the two values and releases the response to the page only when they match.
Preflight Requests
Calls that use methods like PUT or DELETE, custom headers, or JSON bodies trigger a preflight. The browser sends an OPTIONS request first and asks, "May I send this?"
Browser (shop.example.com) API (api.example.com)
| |
| OPTIONS /orders |
| Origin: https://shop.example.com |
| Access-Control-Request-Method: DELETE |
|-----------------------------------------> |
| |
| Access-Control-Allow-Origin: shop... |
| Access-Control-Allow-Methods: DELETE |
| <---------------------------------------- |
| |
| DELETE /orders/42 (real request) |
|-----------------------------------------> |
Key CORS Headers
- Access-Control-Allow-Origin names the permitted website
- Access-Control-Allow-Methods lists permitted HTTP methods
- Access-Control-Allow-Headers lists permitted request headers
- Access-Control-Allow-Credentials allows cookies and authorization data
- Access-Control-Max-Age sets how long the browser caches a preflight answer
Dangerous Misconfigurations
Wildcard With Sensitive Data
The value * lets every website read responses. Public data tolerates this setting. Private data does not.
Reflecting the Origin Header
Some servers copy whatever Origin arrives straight into the response. That habit approves every website while looking like a restricted setup.
Trusting the Null Origin
Sandboxed pages and local files send the origin value null. Approving it opens the door to hostile pages.
Weak Domain Matching
A rule that checks only whether the origin ends with "example.com" also accepts "badexample.com". Compare complete, exact origin strings.
Credentials Without Care
Allowing credentials for broad origin lists lets other websites act as the logged-in user.
Safe Setup Checklist
- Keep a fixed allowlist of exact origins
- Return the matching origin only when it appears on the list
- Add the Vary: Origin header so caches store separate answers
- Limit methods and headers to the minimum needed
- Enable credentials only for trusted origins
- Use a reasonable Max-Age value to reduce preflight traffic
An Important Reminder
CORS protects browsers and nothing else. Command-line tools, scripts, and mobile apps ignore it entirely. Strong authentication and authorization must guard every endpoint, no matter how tight the CORS list looks.
Debugging CORS Errors
Browser consoles show a CORS error when the response lacks the right header. Open the network tab and inspect the failed request. Check the Origin header the browser sent, then read the Access-Control headers in the reply. Preflight failures usually come from a missing OPTIONS handler or a gateway that rejects OPTIONS without credentials. Fix the server configuration instead of installing browser extensions that disable the protection, because those tools hide real problems and put your own machine at risk.
Testing Your Setup
Send requests with a trusted origin, an untrusted origin, a null origin, and a lookalike domain. Confirm that only the trusted origin receives an approval header. Add these checks to automated tests so a later configuration change cannot quietly weaken the policy.
