
A mensagem lê-se mais ou menos assim, e a metade que importa é o fim:
Access to fetch at 'https://api.example.com/orders' from origin
'https://app.example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Leia-a como uma frase sobre quem recusou. O servidor respondeu. O seu pedido saiu do navegador, chegou à outra máquina e voltou com uma resposta. O navegador recusou-se então a entregar essa resposta ao seu JavaScript, porque a resposta não dizia que a sua origem tinha autorização para a ler.
Este único facto afasta quase tudo o que se tenta primeiro. Não há nada avariado na rede. O servidor não está em baixo. Não há nenhuma firewall pelo caminho. A resposta está ali no navegador, e o navegador não a entrega.
Porque é que o navegador faz isto
Sem isso, qualquer página que visite poderia, sem dar nas vistas, fazer pedidos ao seu banco, ao seu webmail ou às ferramentas internas da sua empresa usando os cookies que já estão no navegador, e ler as respostas.
A regra é, portanto: o JavaScript pode enviar um pedido para outra origem, mas só pode ler a resposta se o servidor que responde disser que essa origem o pode fazer. A autorização tem de vir do servidor a quem se pergunta, porque é a única parte que sabe quem deve estar a ler aquilo.
Duas consequências que apanham as pessoas de surpresa:
- Só se aplica ao JavaScript do navegador. O mesmo pedido a partir do curl, do Postman ou do seu próprio backend funciona, porque nenhum deles é um navegador a guardar a sessão de alguém. Um pedido que funciona no Postman e falha na página não é prova de um servidor avariado.
- Desativá-lo no seu navegador não corrige nada. Uma flag ou uma extensão que desliga a verificação faz o erro desaparecer numa máquina enquanto todos os visitantes continuam a recebê-lo. É uma maneira de confirmar o diagnóstico, não uma correção, e usar um navegador assim no dia a dia é mesmo má ideia.
Ler qual das regras falhou
O fim da mensagem dá o nome da regra, e há três com que se vai deparar.
No ‘Access-Control-Allow-Origin’ header is present. (Não está presente qualquer cabeçalho ‘Access-Control-Allow-Origin’.) O caso simples. O servidor não enviou nada sobre autorizações, por isso o navegador assume que não há nenhuma.
Response to preflight request doesn’t pass access control check. (A resposta ao pedido de
preflight não passa a verificação de controlo de acesso.) A falha aconteceu antes sequer de o seu
pedido ser enviado. Tudo o que vá além de um pedido simples, um PUT ou um DELETE, um cabeçalho
próprio como Authorization, um content type JSON, faz o navegador enviar primeiro um pedido
OPTIONS, a perguntar se o verdadeiro é permitido. Se esse OPTIONS devolver um 404, um 500, um
redirecionamento ou um 200 sem os cabeçalhos certos, o pedido verdadeiro nunca chega a acontecer.
*The value of ‘Access-Control-Allow-Origin’ must not be the wildcard ‘’ when credentials mode is
‘include’.** (O valor não pode ser o caráter universal ‘*’ quando são enviadas credenciais.) Está a
enviar cookies, e * não é suficientemente específico para isso. O servidor tem de nomear
exatamente a sua origem e acrescentar Access-Control-Allow-Credentials: true.
Onde vai a correção
No servidor a quem pertence o recurso, em todos os casos. Não na sua página, não no navegador, não num proxy que ponha à frente do seu próprio front end.
Se a API é sua, isso quer dizer devolver Access-Control-Allow-Origin com a origem que quer
permitir e, para os preflights, responder ao OPTIONS com os métodos e cabeçalhos permitidos e um
estado 2xx. Se a API é de outra pessoa e não permite acesso a partir do navegador, então o acesso a
partir do navegador não está disponível: chame-a a partir do seu próprio backend e deixe a sua
página falar com esse.
Session Replay
Extensão gratuita do Chrome. Um clique na página que se está a portar mal captura a captura de ecrã, a consola e o registo de rede, e devolve-lhe um link para colar no ticket.
Em localhost, que é onde a maioria se depara com isto
http://localhost:3000 e http://localhost:8080 são origens diferentes, por isso um front end numa
porta a chamar uma API noutra faz um pedido entre origens e leva o tratamento completo.
A resposta certa em desenvolvimento é um proxy: o seu servidor de desenvolvimento reencaminha /api
para a API, de modo que o navegador vê uma só origem e a questão nunca se põe. Todas as ferramentas
modernas de front end trazem isto de origem e custa uma linha de configuração. A resposta errada é
arrancar o Chrome com a segurança web desativada, porque assim o código só funciona para quem fez o
mesmo.
O que o seu código vê
Nada de útil, que é a parte que torna isto confuso de depurar.
Uma resposta bloqueada não chega como um erro que possa inspecionar. fetch() é rejeitado com o
genérico
TypeError: Failed to fetch,
sem estado e sem corpo, porque deixar a página ler porquê foi bloqueada revelaria justamente a
informação que a regra existe para proteger.
Por isso a mensagem de CORS na consola não é um erro que o seu código tenha apanhado. É o navegador a dizer a si, que desenvolve, o que fez. O seu código não a consegue ver, não a consegue registar e não a consegue reportar.
Isso é bom de saber quando o relato vem de outra pessoa. Um utilizador que apanha uma falha de CORS
vê uma funcionalidade que em silêncio não faz nada, o seu registo de erros anota um Failed to
fetch sem detalhe, e a frase que o teria explicado foi impressa numa consola que ninguém guardou.
Encontrá-lo num relatório
Dois sítios, e quer os dois.
A consola traz a mensagem de CORS com a origem, o URL e a regra que falhou. O painel de rede
mostra o próprio pedido, e, numa falha de preflight, o pedido OPTIONS que está por cima do
verdadeiro, com o seu próprio estado. Abra os cabeçalhos de resposta dele: o que ali falta é todo o
diagnóstico.
Se o pedido não aparecer de todo no painel de rede, o navegador recusou antes de o enviar, o que quer dizer conteúdo misto ou uma extensão, e não CORS.
A versão curta
- O servidor respondeu; o navegador recusou-se a deixar o seu código ler a resposta.
- Só se aplica ao JavaScript do navegador, por isso o curl e o Postman não provam nada.
- O fim da mensagem diz qual das regras falhou. Leia isso, não o princípio.
- As falhas de preflight acontecem antes do seu pedido, num
OPTIONSque o navegador enviou por si. - A correção pertence ao servidor que responde. Desativar a verificação localmente é um diagnóstico, não uma correção.
- Em localhost, use um proxy de desenvolvimento para que haja apenas uma origem.