CORS Explicado Por Que Seu Fetch Funciona no curl, Mas Explode no Navegador
Erros CORS parecem erros do servidor. Não são. São verificações de permissão impostas pelo navegador. Entenda a Política de Origem Igual, as requisições pré-volta e os cinco cabeçalhos que importam — e corrija todos os erros CORS em menos de 5 minutos.
You write a fetch() call. It works perfectly in curl. You open the browser, hit the endpoint, and get slapped with:
Access to fetch at 'https://api.example.com/data' from origin 'https://yourapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
You Google it. Stack Overflow tells you to add a header. You add the header. Different error. You add another header. Now it’s a preflight error. Two hours in, you’re disabling CORS entirely in Chrome with a flag you found in a 2016 answer. You ship it to production like that and hope nobody notices.
This article exists to stop that from happening. CORS takes about 10 minutes to understand correctly, and once you do, you fix it in 2 minutes every time.
It’s Not a Server Bug. It’s a Browser Rule.
This is the single most important thing to understand about CORS: it is enforced entirely by the browser. The server doesn’t block your request. The browser does, after the server already responded.
That’s why curl works. curl doesn’t enforce CORS. Neither does Postman. Neither does your backend calling another backend. CORS only activates when a browser makes a cross-origin request on behalf of a web page.
The server’s job is to say whether it permits cross-origin access via response headers. The browser’s job is to check those headers and decide whether to hand the response to your JavaScript. If the headers aren’t right, the browser discards the response and throws the error — even though the request completed and the server returned data.
Why the Browser Does This (The Same-Origin Policy)
The Same-Origin Policy (SOP) is a browser security rule that prevents JavaScript running on https://evil.com from reading responses from https://yourbank.com. Without it, any site could silently make authenticated requests on your behalf and read the results.
Two URLs have the same origin if their scheme, host, and port all match:
https://app.example.comehttps://api.example.com— different origins (different subdomain)http://example.comehttps://example.com— different origins (different scheme)https://example.comehttps://example.com:8080— different origins (different port)https://example.com/fooehttps://example.com/bar— same origin (path doesn’t count)
CORS is how servers opt in to relaxing SOP for specific origins. Not a bypass — an explicit permission grant.
Simple Requests vs. Preflight Requests
Not every cross-origin request triggers a preflight. The browser splits requests into two categories.
Requisições simples go straight through (no OPTIONS check first). A request is “simple” when it meets all of:
- Method is GET, POST, or HEAD
- Content-Type is
application/x-www-form-urlencoded,multipart/form-data, outext/plain - No custom headers beyond the CORS-safelisted list
Preflighted requests are everything else — any PUT/PATCH/DELETE, any application/json body, any custom header like Authorization ou X-API-Key. Before the actual request, the browser automatically sends an OPTIONS request to ask “do you allow this?”
What a Preflight Actually Looks Like
Here’s a real preflight exchange. Your frontend calls fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' } }).
The browser first sends this OPTIONS request automatically — you never write this code:
OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://yourapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
The server must respond with headers confirming it allows this:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Only if the preflight succeeds does the browser send the actual POST. If any preflight header is missing or wrong, the real request never leaves the browser.
Access-Control-Max-Age: 86400 caches the preflight result for 24 hours so the browser doesn’t repeat this handshake on every request. Worth setting it.
Os Cabeçalhos que realmente importam
Access-Control-Allow-Origin — the only required header. Either a specific origin (https://yourapp.com) ou * for any origin. You cannot use * when sending credentials (cookies, Authorization headers).
Access-Control-Allow-Methods — required for preflighted requests. List every method you want to permit. Always include OPTIONS itself or some servers return 405 on the preflight.
Access-Control-Allow-Headers — required when the request includes custom headers. Must explicitly list every non-safelisted header your frontend sends. Missing Authorization here is responsible for roughly 40% of CORS support tickets.
Access-Control-Allow-Credentials: true — required only when you’re sending cookies or HTTP auth. Must also set credentials: 'include' on the fetch call. When this is true, Allow-Origin must be an explicit origin — * won’t work and you’ll get a second distinct error.
Access-Control-Expose-Headers — response headers your frontend can read. By default, JavaScript can only access a small safelisted set (Content-Type, Cache-Control, etc.). If you need to read a custom header like X-Request-Id in your response handler, add it here.
Five Mistakes That Cost Hours
1. Wildcard with credentials. Contexto Access-Control-Allow-Origin: * and then wondering why credentialed requests still fail. The spec explicitly forbids wildcards when credentials are involved. You must reflect the specific origin: read the Origin request header and echo it back in the response.
2. Not handling OPTIONS at all. Express, FastAPI, and most frameworks don’t automatically respond to OPTIONS requests with CORS headers. If your CORS middleware only runs on actual endpoints and not on the preflight route, every credentialed or non-simple request will fail. The fix: make sure your CORS middleware runs before your router, or handle OPTIONS explicitly.
3. Returning CORS headers only on success. If your server returns a 4xx or 5xx without CORS headers, the browser hides the real status code from JavaScript. Your error handler receives a generic network error with no status. Always return Access-Control-Allow-Origin on every response, not just 200s.
4. Forgetting the port. https://app.example.com e https://app.example.com:3000 are different origins. During local development you’ll frequently hit a backend on port 8080 from a frontend on 3000. Both ports must be in your allowed origins list, or you need a dev proxy.
5. Caching a bad preflight. Access-Control-Max-Age caches the preflight. If you set it to 86400 and then update your allowed headers, browsers with the cached (wrong) preflight will keep failing for up to 24 hours. During development, either set this to 0 or clear it with a hard reload + DevTools cache disable.
Quick Fixes by Scenario
Frontend + backend on different domains, no cookies: Adicionar Access-Control-Allow-Origin: * (or the specific frontend origin) to your server. Done.
Frontend + backend on different domains, with cookies/auth: You need both Access-Control-Allow-Origin: https://yourfrontend.com (explicit, no wildcard) and Access-Control-Allow-Credentials: true on the server, plus credentials: 'include' in your fetch call. Cookies also need SameSite=None; Secure to cross origins.
Local dev hitting a remote API you don’t control: Add a dev proxy in your build tool. Vite: server.proxy. webpack-dev-server: devServer.proxy. Next.js: rewrites. The proxy makes your browser think all requests are same-origin; CORS never triggers.
API Gateway / CDN in front: Make sure the OPTIONS preflight isn’t getting swallowed before it reaches your origin. AWS API Gateway and CloudFront both have CORS settings that can conflict with your app-level headers — if you set headers in both places you’ll often get duplicated header values, which also fails.
If you need to verify the exact headers your server is returning, the Construtor e Validador de Cabeçalhos CORS on IO Tools lets you construct and inspect the full header set without writing a single line of code. Useful for auditing what a backend actually returns vs. what you think it returns.
CORS errors look like server bugs because the error message mentions the server. They’re browser-enforced permission checks — and once you know which header is missing and why, every CORS error resolves in minutes. Build the right headers with the CORS Headers Builder or test your raw request structure with the Construtor de Comandos cURL to rule out client-side mistakes before touching server config.
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente foi adicionado em 20 de junho de 2026
