🌍 CORS Tester
Send a CORS preflight (OPTIONS) and real request from a chosen Origin to verify Access-Control-Allow-Origin / Methods / Headers / Credentials / Max-Age.
📚 CORS Basics
• Browsers block fetch / XHR to different origins. That is the same-origin policy.
• CORS lets the server declare "allowed origins" via Access-Control-Allow-Origin.
• A preflight request is an OPTIONS request that the browser sends before the actual request. It occurs when using custom headers or non-simple methods (PUT, DELETE, etc.).
• Access-Control-Allow-Credentials: true cannot be combined with Access-Control-Allow-Origin: * — must use a specific origin.
📖 What this check tells you
This check sends a real request carrying an Origin header plus a preflight OPTIONS, and reports the Access-Control-* headers that come back. The premise to hold on to is that only browsers enforce CORS — curl and server-to-server calls ignore it entirely. CORS is therefore not a mechanism that protects your API; it is a mechanism that stops a malicious site from silently using credentials already stored in the user browser.
| Item | What it means | How to fix it |
|---|---|---|
| Listing multiple origins | Access-Control-Allow-Origin accepts exactly one origin, or *. A comma-separated list is invalid per the specification and the browser rejects the whole header. It is a very common cause of the it-is-configured-but-still-fails complaint. |
Compare the request Origin against a whitelist on the server and echo back only the single value that matched. Because the response now varies by origin, always add Vary: Origin. Forget it and your CDN caches the first response and serves it to a different origin. |
| A preflight fires on every request | Sending Content-Type: application/json, adding Authorization or a custom header, or using a method other than GET, POST or HEAD makes the request non-simple, so an extra OPTIONS round trip precedes the real one. That doubles the round trips per API call, which is felt directly on a high-latency connection. |
List every header you actually send in Access-Control-Allow-Headers — one missing entry fails the preflight — and cache the preflight result with Access-Control-Max-Age. Chrome caps it at 7200 seconds, so anything larger is silently clamped. |
| Error responses carry no CORS headers | Many frameworks bypass their middleware when an exception produces a 500, so the response goes out without Access-Control-Allow-Origin. The browser then refuses to expose the body, and the console shows nothing but a CORS error. The real cause — a server-side exception — is completely hidden. |
Attach the same CORS headers to your exception handler and error_page responses. When triaging, the first step is to look at the actual status code in the Network tab. If it is a 500 or a 404, CORS configuration is irrelevant and the fix belongs in the application. |
CORS is not authorisation. With or without Access-Control-Allow-Origin: *, anyone can still hit the API with curl. If there is data worth protecting, implement token authentication or session validation. The other common misconception is that configuring CORS also prevents CSRF — it does not, because <form> submissions and <img> loads fall outside the reach of CORS entirely. CSRF needs SameSite cookies and a CSRF token of its own.