Free tool · no signup
CORS tester and config generator
Describe the request your page makes, paste the responses, and see whether the browser will allow it. If not, you get the exact reason and the header to change.
By the Bug Circuit security team · Last updated
The request your page makes
- The browser sends a preflight OPTIONS request first, because of the method PUT and the header authorization and the content type application/json.
The request includes credentials, but the response does not send Access-Control-Allow-Credentials: true.
Fix: Send Access-Control-Allow-Credentials: true, or stop sending credentials from the page.
PUT is not listed in Access-Control-Allow-Methods (GET, POST).
Fix: Add PUT to Access-Control-Allow-Methods.
The request header authorization is not allowed by Access-Control-Allow-Headers (content-type).
Fix: Add authorization to Access-Control-Allow-Headers. A * does not cover Authorization, it must be named. A * does not work when credentials are sent.
The request includes credentials, but the response does not send Access-Control-Allow-Credentials: true.
Fix: Send Access-Control-Allow-Credentials: true, or stop sending credentials from the page.
- The preflight has no Access-Control-Max-Age, so the browser repeats it for every request (up to the browser default of 5 seconds, or 2 hours in Chrome). Add a value such as 600 to cut the extra requests.
- The response names one origin but has no Vary: Origin. A cache can then serve the wrong origin's answer to another site. Add Vary: Origin.
How it works
What CORS is, and is not
Browsers stop a page on one origin (scheme, host and port) from reading responses from another unless the other server agrees. That agreement is CORS, and it is enforced by the browser only. The request still reaches the server, which is why CORS does not protect an API from being called by anyone, and why a server that allows every origin is not a security hole unless the response is private to the user.
A simple request, such as a GET or a form POST with no special headers, is sent directly, and the browser then checks the Access-Control-Allow-Origin header in the response. Any other request first triggers a preflight: an OPTIONS request that asks the server whether the real one is allowed.
When a preflight happens, and what it must say
A preflight is sent when the method is not GET, HEAD or POST, when the request carries a header other than the few safe ones (Authorization and X-Api-Key are the usual triggers), or when the body type is not form data or plain text, which includes JSON. The server must answer with a 2xx status and the right headers: Access-Control-Allow-Origin with your origin, Access-Control-Allow-Methods listing the method, and Access-Control-Allow-Headers listing each custom header. When credentials are sent, it must also send Access-Control-Allow-Credentials: true.
Two rules catch most people. A wildcard * does not work when cookies or credentials are sent, so the server must name the exact origin. And a * in Access-Control-Allow-Headers does not cover the Authorization header, which has to be listed by name.
Fixing it safely
Compare the incoming Origin header against a fixed list on the server, and send back only the one that matches, with Vary: Origin so caches do not mix answers up. Do not copy the Origin header back without checking it, because that allows every site while looking strict. The generator tab writes an allow-list for Nginx, Apache and Express. For local development, a proxy in your dev server avoids CORS altogether.
Worked examples
A JSON PUT with a login token
Page https://app.example.com calls PUT https://api.example.com/items with Authorization and a JSON body.
Preflight needed: the method PUT and the header authorization. The preflight must allow PUT, allow the authorization header, and send Allow-Origin: https://app.example.com.
Either condition alone triggers a preflight. Together they make the server list both the method and the header.
The wildcard with cookies
credentials: include, and the server sends Access-Control-Allow-Origin: *.
Blocked: the request includes credentials, and the server answers *. Fix: send the exact origin, and add Vary: Origin.
Browsers refuse the combination, whatever the other headers say.
Allowed with a preflight cache
A correct preflight and response, with Access-Control-Max-Age: 600.
The browser allows it.
Max-Age lets the browser skip the preflight for ten minutes, which cuts the extra requests.
Frequently asked questions
What does the CORS error mean?
Is it safe to set Access-Control-Allow-Origin to *?
Why does my request send an OPTIONS call first?
Does CORS protect my API?
Why is Authorization not covered by a wildcard?
What is Vary: Origin for?
How do I avoid CORS during development?
Keep reading
See what attackers see, for free
Run the free passive check on your website. No login, no impact on your site, results in seconds.
Ready for the full manual audit? See pricing