O que são CORS e por que tudo quebra quando você testa localmente
Você já deve ter visto aquele erro de consola quando faz uma requisição fetch de um frontend em um domínio para uma API em outro domínio. A mensagem é sempre a mesma, em versões ligeiramente diferentes, e ninguém gosta de a ver. O problema não é o código. O problema é que o navegador bloqueia a resposta antes mesmo do JavaScript a conseguir ler. CORS, ou Cross-Origin Resource Sharing, é o mecanismo que o navegador aplica por defeito a todas as requisições entre origens diferentes. Ele existe porque, sem ele, qualquer site malicioso poderia fazer requisições em nome do utilizador para serviços bancários, redes sociais ou painéis de administração sem autorização. O navegador protege o utilizador. O problema é que essa proteção também protege contra uso legítimo, e a configuração precisa ser clara o suficiente para não bloquear aplicações inteiras.
No lado técnico, quando o navegador faz uma requisição cross-origin, ele insere automaticamente um header Origin no pedido. O servidor responde com um conjunto de headers específicos que dizem ao navegador se a origem tem permissão para acessar os recursos. Se esses headers não estiverem presentes ou não corresponderem ao valor de Origin, a resposta é bloqueada no nível do cliente.
porque é importante configurar CORS corretamente
A maioria das pessoas que configura CORS não sabe realmente o que está a configurar. Elas colocam Access-Control-Allow-Origin: * e resolvem. Funciona. Até precisarem de autenticação com cookies ou credenciais especiais, altura em que o navegador recusa automaticamente qualquer wildcard. Esse é o primeiro sinal de que a configuração não está correta, mas muitos nunca percebem. O que muitos desenvolvedores não entendem é que CORS é puramente uma exigência do navegador. O servidor em si não faz nada de especial por causa dele. O servidor recebe a requisição normalmente, processa e retorna a resposta. É apenas o navegador que, ao receber a resposta, verifica os headers e decide se a API do JavaScript pode acessar os dados. Isso significa que ferramentas como curl ou Postman nunca vão mostrar erros de CORS, mesmo quando o navegador vai.
Um detalhe técnico importante é a diferença entre requisições simples e prévia. Requisições simples são aquelas que usam métodos GET ou POST com tipos de conteúdo específicos e headers padronizados. Nesse caso, o navegador faz a requisição diretamente. Requisições mais complexas — como PUT, DELETE, ou headers personalizados, ou content-type JSON em muitos casos — disparam um OPTIONS prévio. O navegador faz uma requisição OPTIONS antes da real para perguntar ao servidor se a operação é permitida. Se o servidor não responder ao OPTIONS corretamente, a requisição original nem chega a ser enviada. Eu configurei CORS uma vez num projeto que integrava um serviço de upload de ficheiros com um CDN em outro domínio. A aplicação funcionava perfeitamente em produção, mas no ambiente de desenvolvimento local falhava consistentemente. O problema era que o header de origem estava hardcoded para o domínio de produção, e o navegador rejeitava a origem localhost:3000. A solução foi criar uma variável de ambiente no servidor que refletisse a origem atual, lendo o header Origin recebido e respondendo com o mesmo valor quando estivesse na lista permitida, em vez de usar um valor fixo.
Outro ponto que raramente é mencionado é o comportamento dos browsers modernos com Access-Control-Allow-Credentials: true. Quando esse header está presente, o wildcard * no allow-origin é explicitamente proibido. O navegador rejeita a resposta. Isso foi introduzido deliberadamente para impedir que cookies sejam enviados para origens arbitrárias, mas muitos desenvolvedores levam dias para perceber que precisam listar cada origem explicitamente nessa situação. Se estiver usando Node.js com Express, a configuração mais comum envolve o pacote cors. Ele permite configurar origins dinâmicas, métodos permitidos, headers permitidos e o flag de credenciais. A desvantagem é que, com muitas origens, a lista pode ficar extensa e difícil de manter. Em ambientes com múltiplos domínios, especialmente com staging e produção compartilhando a mesma API, a solução mais limpa é permitir apenas as origens que correspondem a um padrão regex ou validar contra uma lista branca em configuração.
Em aplicações Vue ou React que rodam em domínios diferentes da API, o uso de proxy no desenvolvimento é a abordagem mais prática. O servidor de desenvolvimento encaminha as requisições para a API real, mantendo tudo na mesma origem. Isso elimina completamente a necessidade de CORS durante o desenvolvimento local. No entanto, isso não resolve o problema em produção, onde as origens continuam diferentes.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Erros comuns que causam problemas persistentes
O primeiro erro que vejo constantemente é a configuração de CORS no middleware errado ou depois do middleware de roteamento. Se o CORS não for aplicado antes do middleware que lida com a rota, as requisições OPTIONS de prévia podem ser interceptadas por rotas que não reconhecem o verbo e retornar 404 em vez dos headers adequados. O segundo erro é a suposição de que configurar CORS no backend é suficiente. Se a aplicação usa certificados autoassinados em HTTPS ou se há um balanceador de carga configurado incorretamente, o header Origin pode ser cortado ou modificado antes de chegar ao servidor. Nesses casos, o servidor vê um origin diferente do que o navegador espera, e o cabeçalho de resposta não corresponde, resultando no bloqueio.
Um terceiro erro relevante ocorre com aplicações monorepo onde frontend e backend estão em subdomínios diferentes do mesmo domínio base. Técnicas como definir Access-Control-Allow-Origin com o domínio pai não funcionam porque o navegador compara a origem completa, incluindo subdomínio. app.example.com e api.example.com são origens distintas, mesmo que pertençam ao mesmo domínio base. A solução é listar explicitamente cada subdomínio na configuração. Há ainda o caso das requisições prévias com métodos PUT ou DELETE. Muitos desenvolvedores esquecem que o servidor precisa responder ao OPTIONS com os mesmos headers de permissão que usaria para o método real. Se o OPTIONS responder corretamente mas o PUT não, o navegador já bloqueou a requisição na fase de prévia e o código do PUT nunca é executado.
Como configurar CORS sem dor de cabeça
A abordagem mais segura começa com uma lista explícita de origens permitidas. Evite wildcards, mesmo que a aplicação pareça ser interna. Wildcards funcionam para requisições sem credenciais, mas entram em conflito imediato assim que você precisar de cookies, autenticação HTTP básica ou tokens em headers customizados. Use variações dinâmicas do header Origin quando possível. Isso significa ler o valor de Origin recebido na requisição e retorná-lo no Access-Control-Allow-Origin quando corresponder a uma lista permitida. Esse padrão é mais seguro do que hardcodar origens e evita problemas de configuração em múltiplos ambientes.
Configure também Access-Control-Allow-Methods com os métodos que realmente pretende usar. Deixar isso em branco ou usar um wildcard genérico expõe a API a requisições indevidas e pode causar problemas de segurança em auditorias. Access-Control-Allow-Headers deve listar apenas os headers que a aplicação precisa enviar, não todos os possíveis. Se o projeto utiliza GraphQL, a configuração de CORS precisa considerar que a maioria dos clientes GraphQL envia requisições POST com content-type JSON. Muitas configurações de CORS mal ajustadas interpretam isso como requisição complexa e disparam prévia desnecessariamente, o que aumenta a latência percebida e pode causar erros se o endpoint OPTIONS não estiver configurado no esquema GraphQL.
Para monitorar problemas de CORS em produção, configure logs no servidor que registrem o header Origin de cada requisição recebida. Isso permite identificar quais origens estão tentando acessar a API e detectar padrões de requisições bloqueadas que podem indicar configurações erradas no frontend ou tentativas de acesso não autorizadas. O tempo médio de resolução de um problema de CORS mal configurado varia entre 20 minutos e 2 horas, dependendo de quão profunda é a configuração e se há múltiplas origens envolvidas. Na maioria dos casos, o problema está em apenas um lugar: a lista de origens permitidas não inclui a origem que está fazendo a requisição no momento.
CORS não é um problema de segurança do servidor. É um problema de configuração que o navegador impõe aos clientes. Entender essa distinção muda completamente a forma como você aborda o debugging. O servidor está funcionando corretamente. O navegador está sendo rigoroso demais. A solução está em fazer os dois lados conversar na mesma linguagem.