CORS explicado: por que o navegador bloqueia sua requisição
CORS não é o servidor te bloqueando: é o navegador protegendo o usuário. Entenda a mecânica de origens, preflight e cabeçalhos que libera a requisição.
Neste artigo
Poucos erros geram tanta frustração em quem começa a integrar front-end com API quanto a mensagem de que uma requisição foi bloqueada por política de CORS. A reação natural é achar que o servidor recusou o pedido, ou que há algo errado no código da requisição. Na maioria das vezes, nenhuma das duas coisas é verdade: a requisição saiu, o servidor até respondeu, mas o navegador se recusou a entregar a resposta ao seu código JavaScript. Entender que o bloqueio é uma decisão do navegador, e não do servidor, é o primeiro passo para parar de brigar com o CORS e começar a configurá-lo corretamente.
O CORS existe por um motivo sólido de segurança. Sem ele, qualquer site que você visitasse poderia, em segundo plano, fazer requisições autenticadas para o seu banco, para o seu e-mail, para qualquer serviço em que você estivesse logado, e ler as respostas. A política que impede isso é antiga e fundamental na web, e o CORS é justamente o mecanismo controlado que permite relaxar essa política de forma segura, quando as duas partes concordam. Ou seja: o CORS não é o obstáculo, é a porta oficial.
Neste artigo vamos desmontar a mecânica peça por peça: o que é uma origem, por que requisições entre origens diferentes são tratadas com desconfiança, o que é a verificação prévia que o navegador faz, e quais cabeçalhos o servidor precisa enviar para que a resposta chegue ao seu código. No fim, o erro de CORS deixa de ser um mistério e vira um checklist.
O que é uma origem#
Tudo no CORS gira em torno do conceito de origem. Uma origem é a combinação de três coisas: o esquema (se é uma conexão comum ou cifrada), o host (o domínio) e a porta. Duas URLs têm a mesma origem apenas se as três partes forem idênticas. Trocar o esquema muda a origem; usar um subdomínio diferente muda a origem; usar uma porta diferente muda a origem. Essa definição estrita é proposital — a segurança depende de não haver ambiguidade sobre o que conta como "o mesmo lugar".
A partir dessa definição, o navegador aplica uma regra histórica: a política de mesma origem. Por padrão, um documento carregado de uma origem pode fazer requisições livremente para a própria origem, mas requisições para outras origens são tratadas com restrições. O código pode até disparar a requisição, mas o navegador controla se a resposta pode ser lida. É essa política que o CORS gerencia.
Por que a mesma origem importa tanto#
A razão é o modelo de credenciais da web. Quando você está logado em um serviço, o navegador guarda um cookie e o envia automaticamente em toda requisição para aquele domínio. Se qualquer página pudesse fazer requisições para qualquer domínio e ler as respostas, um site malicioso conseguiria, sem você perceber, agir em seu nome em todos os serviços onde você tem sessão ativa e roubar os dados retornados. A política de mesma origem quebra esse ataque na raiz: a requisição pode até ser enviada com os cookies, mas o script atacante não consegue ler o que voltou. O CORS entra como a forma de abrir exceções controladas a essa regra.
Requisições simples e o cabeçalho de origem#
Nem toda requisição entre origens passa pelo mesmo tratamento. O navegador classifica algumas como "simples" — aquelas que usam métodos básicos, com apenas um conjunto restrito de cabeçalhos e tipos de conteúdo comuns em formulários. Para essas, o navegador envia a requisição diretamente, incluindo um cabeçalho Origin que identifica de onde a requisição partiu.
O servidor, ao receber essa requisição, decide se aceita responder para aquela origem. Se aceita, ele inclui na resposta o cabeçalho Access-Control-Allow-Origin com o valor da origem permitida — ou com um curinga que libera qualquer uma. O navegador então verifica: a resposta trouxe esse cabeçalho e ele autoriza minha origem? Se sim, entrega a resposta ao código. Se não, bloqueia — e é aqui que aparece a mensagem de erro. Repare no detalhe crucial: a requisição foi feita, o servidor processou e respondeu, mas o navegador reteve a resposta porque faltava a autorização explícita.
A verificação prévia: o preflight#
Quando a requisição não se enquadra na categoria simples — porque usa um método como PUT ou DELETE, ou porque carrega cabeçalhos personalizados como um token de autorização, ou porque envia JSON — o navegador não confia em mandar direto. Antes da requisição de verdade, ele envia uma requisição preliminar de verificação, chamada de preflight, usando o método OPTIONS.
Esse preflight é uma pergunta antecipada: o navegador diz ao servidor "pretendo fazer uma requisição com tal método e tais cabeçalhos, a partir de tal origem — você permite?". Para isso ele envia cabeçalhos como Access-Control-Request-Method, informando o método pretendido, e Access-Control-Request-Headers, listando os cabeçalhos que a requisição real usará. O servidor responde declarando o que autoriza.
O que o servidor precisa responder no preflight#
A resposta ao preflight precisa carregar um conjunto de cabeçalhos para que o navegador libere a requisição real:
Access-Control-Allow-Originconfirma que aquela origem específica está autorizada a fazer a requisição.Access-Control-Allow-Methodslista os métodos permitidos, e o método pretendido precisa estar nessa lista.Access-Control-Allow-Headerslista os cabeçalhos permitidos, e todo cabeçalho personalizado que a requisição real vai enviar precisa constar aqui — é a causa mais comum de preflight falhar quando se adiciona um cabeçalho de autenticação.Access-Control-Max-Agediz por quanto tempo o navegador pode guardar o resultado desse preflight, evitando repeti-lo a cada requisição durante um período.
Só depois que o navegador recebe um preflight satisfatório é que ele envia a requisição real. Se qualquer peça faltar — o método não estiver na lista, o cabeçalho não estiver autorizado, a origem não bater — o navegador nem chega a fazer a requisição principal, e o erro aparece já na etapa de verificação.
Credenciais: o caso especial dos cookies#
Existe uma camada adicional de cuidado quando a requisição precisa carregar credenciais, como cookies ou cabeçalhos de autenticação baseados em sessão. Por padrão, requisições entre origens não enviam cookies. Para que enviem, o código do cliente precisa sinalizar explicitamente que quer incluir credenciais, e o servidor precisa responder com o cabeçalho Access-Control-Allow-Credentials com valor verdadeiro.
E aqui há uma restrição que pega muita gente de surpresa: quando credenciais estão envolvidas, o servidor não pode usar o curinga no Access-Control-Allow-Origin. Ele é obrigado a devolver a origem específica e exata. A lógica é de segurança: permitir credenciais para qualquer origem seria abrir exatamente o buraco que a política de mesma origem fecha. Portanto, em cenários com sessão baseada em cookie, o servidor precisa ecoar dinamicamente a origem autorizada, verificando-a contra uma lista permitida, em vez de liberar geral.
Por que o erro engana tanto#
A grande fonte de confusão é a assimetria entre quem faz o bloqueio e quem parece culpado. O erro aparece no console do navegador, do lado do front-end, dando a impressão de que o problema está ali. Mas o CORS é resolvido inteiramente por cabeçalhos que o servidor precisa enviar. Não há nada que o código JavaScript possa fazer para "contornar" o CORS a partir do cliente: a decisão está nas mãos do navegador, que obedece aos cabeçalhos vindos do servidor. Tentar ajustar a requisição no front-end para resolver CORS é procurar a chave debaixo do poste errado.
Isso também explica por que a mesma requisição funciona quando testada por uma ferramenta de linha de comando e falha no navegador. Ferramentas fora do navegador não aplicam a política de mesma origem — elas não têm o contexto de um usuário logado a proteger. O CORS é uma proteção específica do ambiente do navegador, para o cenário em que código não confiável roda com as credenciais do usuário. Fora desse contexto, a restrição simplesmente não existe.
O papel dos proxies e do mesmo domínio#
Uma solução legítima e comum é fazer com que o front-end converse com uma camada intermediária na mesma origem — um servidor próprio que, por baixo, chama a API externa. Como a requisição do navegador vai para a mesma origem, não há CORS a resolver; e a chamada de servidor para servidor também não passa pela política de mesma origem. Além de resolver o CORS de forma limpa, esse arranjo tem a vantagem de manter segredos e credenciais no servidor, longe do código que roda no navegador. Não é um truque para enganar o CORS; é reconhecer que o CORS protege o navegador, e que servidores conversam por outras regras.
Fechando o raciocínio#
O CORS deixa de ser assustador quando você inverte a forma de pensar sobre ele. Ele não é um obstáculo que o servidor coloca contra você; é uma proteção que o navegador oferece ao usuário, impedindo que sites arbitrários leiam respostas de outros domínios usando as credenciais da vítima. O mecanismo é uma negociação por cabeçalhos: o navegador anuncia a origem e, quando necessário, pergunta antes com um preflight; o servidor responde declarando quais origens, métodos e cabeçalhos autoriza.
Com esse modelo na cabeça, depurar CORS vira um processo direto. A requisição é simples ou dispara preflight? O servidor está devolvendo o Access-Control-Allow-Origin correto? Se há preflight, o método e os cabeçalhos pretendidos estão nas listas permitidas? Há credenciais envolvidas, e nesse caso a origem é específica em vez de curinga? Cada uma dessas perguntas aponta para um cabeçalho concreto do lado do servidor. O erro que antes parecia arbitrário se resolve conferindo, um por um, se o servidor está enviando o que o navegador precisa para liberar a resposta. E, uma vez que você entende que o navegador está do lado do usuário, a lógica inteira do CORS deixa de ser um empecilho e passa a fazer todo o sentido.