Seu frontend roda em http://localhost:5173, sua API em http://localhost:4000, e a chamada fetch que funcionava sem problemas no Postman falha no navegador com uma parede de texto vermelho: Access to fetch at 'http://localhost:4000/api/users' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Seu servidor pode muito bem ter processado a requisição e retornado um 200. O navegador simplesmente se recusou a entregar a resposta ao seu JavaScript.
Essa recusa é a same-origin policy. CORS é a forma como um servidor diz ao navegador para relaxá-la para chamadores específicos, e cada correção abaixo se resume a um header de resposta que o navegador não recebeu.
O que a same-origin policy realmente bloqueia
Uma origin é a combinação de esquema, host e porta. http://localhost:5173 e http://localhost:4000 são origins diferentes porque a porta muda, mesmo com host e esquema iguais. https://api.example.com e http://api.example.com também são origins diferentes, porque o esquema muda: uma passa por TLS e a outra não.
A same-origin policy é uma regra do navegador, não do servidor. Um script rodando em uma origin não pode ler a resposta de uma requisição feita a uma origin diferente, a menos que essa origin permita explicitamente. Em uma GET simples, a requisição sai do mesmo jeito e o servidor a processa do mesmo jeito: você a vê nos logs do servidor e na aba Network com um código de status real. O que o navegador retém é o corpo da resposta. É por isso que um erro CORS parece uma falha do lado do cliente enquanto o servidor não reporta nada de errado.
O objetivo é impedir que uma página em evil.example use sua sessão autenticada no navegador para ler as respostas da API do seu banco. Sem essa regra, qualquer site que você visitar poderia enviar requisições autenticadas para qualquer outro site e ler as respostas.
O que CORS permite, e o que ele não protege
Cross-Origin Resource Sharing é uma exceção opt-in a essa regra. O servidor adiciona headers de resposta indicando quais origins podem ler suas respostas, e o navegador os respeita em vez de bloquear.
O servidor não recusa nada. Os headers CORS são instruções para o navegador, não controle de acesso. Uma requisição curl, uma chamada backend-to-backend ou um app mobile nativo chegam ao mesmo endpoint sem nenhuma verificação CORS. Se sua API precisa de proteção, isso é assunto de autenticação e autorização.
Quando o navegador envia uma requisição preflight CORS
O navegador classifica cada requisição cross-origin em um de dois tipos, e só um deles pede permissão antes.
Uma requisição é simple quando tudo isto é verdade:
- O método é
GET,HEADouPOST. - Os únicos headers definidos são os que o navegador define automaticamente, mais uma lista curta permitida (
Accept,Accept-Language,Content-Language,Content-Type,Range). - Se
Content-Typeestiver definido, seu valor éapplication/x-www-form-urlencoded,multipart/form-dataoutext/plain.
Uma requisição simple sai direto, e o navegador verifica os headers de resposta para decidir se seu script recebe o body. Tudo o resto é preflighted: um PUT ou DELETE, um body JSON (Content-Type: application/json), ou um header personalizado como Authorization ou X-API-Key. Antes de enviar a requisição real, o navegador envia uma requisição OPTIONS perguntando ao servidor o que ele permite:
| |
O servidor responde com o que permite. Só se essa resposta cobrir a requisição real é que o navegador a envia:
| |
Access-Control-Max-Age indica por quantos segundos o navegador pode guardar essa resposta em cache e pular o preflight na próxima requisição equivalente. Uma API JSON que envia um bearer token dispara um preflight em quase toda chamada, então esse header economiza uma ida e volta a cada vez. Os navegadores limitam isso: o Chromium não guarda em cache por mais de 7200 segundos, seja qual for o valor enviado.
Headers de resposta CORS e o que cada um faz
| Header | O que faz |
|---|---|
Access-Control-Allow-Origin | A origin autorizada a ler a resposta: uma origin literal, ou * para qualquer origin. * é rejeitado em requisições com credenciais. |
Access-Control-Allow-Methods | Os métodos permitidos neste recurso, retornados na resposta do preflight. |
Access-Control-Allow-Headers | Os headers de requisição permitidos, retornados na resposta do preflight. Authorization precisa ser listado pelo nome; * não o cobre. |
Access-Control-Allow-Credentials | true permite que o navegador exponha a resposta a uma requisição com credenciais (cookies, autenticação HTTP). Exige uma origin literal, não *. |
Access-Control-Expose-Headers | Headers de resposta, além da safelist padrão, que o JavaScript pode ler. |
A regra das credenciais é a que mais confunde. “Credenciais” aqui significa cookies e autenticação HTTP gerenciada pelo navegador, enviadas quando você chama fetch(url, { credentials: 'include' }). Nessa requisição, o servidor precisa enviar Access-Control-Allow-Credentials: true e indicar a origin exata em Access-Control-Allow-Origin. Uma resposta com credenciais e Access-Control-Allow-Origin: * é rejeitada mesmo que todos os outros headers estejam corretos.
Um bearer token que você mesmo define em um header Authorization não é uma credencial nesse sentido. É um header personalizado: dispara um preflight e precisa aparecer em Access-Control-Allow-Headers, mas não precisa de Allow-Credentials.
Como corrigir erros CORS no Express
O pacote cors define esses headers para você.
| |
| |
Em produção você costuma ter mais de um frontend legítimo. Passe um array ou uma função em vez de uma única string:
| |
Montado com app.use, o cors() responde ao preflight OPTIONS de cada rota. Se você o montar só em rotas específicas que usam um método diferente de GET, adicione app.options("/api/users", cors()) para que o preflight desse caminho também receba resposta.
Como habilitar CORS no ASP.NET Core
Registre uma policy nomeada em Program.cs e chame UseCors entre UseRouting e UseAuthorization. A ordem importa: um preflight não carrega autenticação, então se a autorização rodar antes, ela rejeita a requisição OPTIONS antes que o CORS possa responder.
| |
WithOrigins só aceita origins literais, pelo mesmo motivo do header em si. Combine AllowCredentials() com AllowAnyOrigin() e o ASP.NET Core lança uma InvalidOperationException em vez de enviar uma resposta que o navegador rejeitaria de qualquer forma. Para controle por endpoint, coloque [EnableCors("_allowFrontend")] em um controller ou uma action e chame app.UseCors() sem nome de policy: o middleware ainda precisa estar no pipeline para o atributo funcionar.
Como evitar CORS em desenvolvimento com um proxy do Vite
No seu notebook, a solução mais simples é parar de fazer uma requisição cross-origin de vez. Vite, webpack-dev-server e a maioria dos outros dev servers conseguem fazer proxy de um caminho para seu backend, assim o navegador só fala com uma origin:
| |
Uma fetch para /api/users agora vai para o dev server do Vite em http://localhost:5173, que a repassa para http://localhost:4000 e retorna a resposta. O navegador vê apenas uma origin, então não há nada para o CORS bloquear.
Isso é uma conveniência de desenvolvimento. A versão de produção da mesma ideia é um reverse proxy que serve o frontend e a API a partir de uma única origin pública, eliminando de vez a necessidade de headers CORS nessa API.
Workarounds de CORS que você não deve usar
Cada um destes faz o texto vermelho sumir para você e deixa o problema real, um header que sua API não está enviando, no lugar:
Access-Control-Allow-Origin: *em um endpoint que usa cookies. O navegador rejeita a resposta com credenciais, então isso nem corrige o erro. Tire os cookies para o wildcard funcionar e você fica com um endpoint que responde a qualquer site da internet sem nenhuma verificação de origin.- Uma extensão de navegador “CORS unblock”. Ela altera as respostas dentro do seu próprio navegador. O erro some para você e continua ali para todo usuário real, incluindo um colega testando o staging de outra máquina.
- Abrir o Chrome com
--disable-web-security. Isso desativa a same-origin policy para todas as abas dessa instância do navegador, não só o seu app, enquanto ela ficar aberta. Uma aba esquecida em um site malicioso anula exatamente a proteção que a política existe para dar.
Qual correção de CORS usar
Se o erro só aparece localmente, contra um backend em outra porta, use o proxy do dev server e pule a configuração de CORS por completo.
Se usuários reais vão chamar sua API cross-origin a partir de um domínio de frontend separado ou de um site terceiro, defina Access-Control-Allow-Origin com as origins exatas que você pretende atender. Adicione Access-Control-Allow-Credentials só se o navegador enviar cookies. Depois abra a aba Network do DevTools e confira a requisição OPTIONS antes de supor que funcionou: um 2xx com Allow-Origin, Allow-Methods e Allow-Headers cobrindo sua requisição real significa que a requisição seguinte vai passar.