CORS explicado: preflight, headers e como resolver erros de CORS
CORS explicado
CORS explicado: preflight, headers, erros e correções | We.Inc
Como o CORS funciona: requisições simples e preflight, cada header Access-Control, regras de credenciais, os erros comuns no console e a correção no servidor para Express, Next.js e nginx.
CORS (cross-origin resource sharing) é a forma de o servidor dizer ao navegador que o JavaScript de outra origem pode ler as respostas dele. O servidor responde com Access-Control-Allow-Origin e headers relacionados. Requisições com outros métodos, headers personalizados ou corpo em JSON enviam antes um preflight OPTIONS. A correção sempre fica no servidor que você está chamando, nunca na página.
Os headers de resposta do CORS em resumo
Header
O que ele permite
Access-Control-Allow-Origin
Qual origem pode ler a resposta (uma origem ou *)
Access-Control-Allow-Methods
Métodos permitidos depois de um preflight
Access-Control-Allow-Headers
Headers de requisição permitidos depois de um preflight
Access-Control-Allow-Credentials
Cookies e autenticação enviados com a requisição (o valor precisa ser true)
Access-Control-Expose-Headers
Headers de resposta que o script pode ler
Access-Control-Max-Age
Segundos em que a resposta do preflight pode ficar em cache
Um erro de CORS parece indicar que o seu código está quebrado, mas em geral a requisição chegou ao servidor sem problema nenhum. O navegador apenas se recusou a entregar a resposta ao seu script. O simulador abaixo permite descrever uma requisição e a configuração do servidor e mostra se um preflight é disparado, se o navegador deixa a resposta passar e qual mensagem você veria no console.
Defina a origem da página, o método, algum header personalizado e se os cookies são enviados.
Defina o que o servidor responde: origem permitida, métodos, headers e a flag de credenciais.
Leia o veredito: se um preflight é necessário, se ele passa e, em caso de falha, o motivo exibido no console.
Copie o trecho de servidor correspondente abaixo e adicione-o à API, não ao front-end.
Quando houver cookies, devolva a origem exata da requisição a partir de uma lista de permissões, porque o curinga é recusado com credenciais.
Adicione Vary: Origin sempre que a origem permitida mudar a cada requisição, para que os caches não sirvam a resposta de um site a outro.
Responda ao OPTIONS com status 2xx e sem redirecionamento. Um preflight que cai em um redirecionamento de login falha sempre.
Desligar o CORS no seu próprio navegador só esconde o problema na sua máquina. Os visitantes continuam vendo o erro.
Chamadas de servidor para servidor nunca envolvem CORS. Se você não controla a API, chame-a a partir do seu próprio backend.
Perguntas frequentes
Por que o Postman ou o curl funcionam quando o navegador falha?
O CORS é aplicado apenas pelos navegadores, para impedir que os scripts de um site leiam os dados de outro site usando os seus cookies. Ferramentas como o curl não executam código de página não confiável, então o ignoram. A resposta do servidor foi idêntica; só o navegador decidiu retê-la.
O que dispara uma requisição de preflight?
Qualquer método que não seja GET, HEAD ou POST, qualquer header fora dos poucos da lista segura (Accept, Accept-Language, Content-Language, Content-Type, Range), ou um Content-Type diferente de form encoding, multipart ou text/plain. Enviar application/json é o gatilho mais comum.
Dá para resolver o CORS pelo frontend?
Não. Definir headers Access-Control na sua requisição não faz nada, e mode: no-cors devolve uma resposta opaca que o seu script não consegue ler. Ou a API adiciona os headers, ou o seu próprio servidor busca os dados e os repassa.
Posso usar Access-Control-Allow-Origin: * com cookies?
Não. Quando a requisição leva credenciais, o navegador rejeita uma origem curinga e exige a origem exata mais Access-Control-Allow-Credentials: true. O mesmo vale para curingas nas listas de métodos, headers e expose headers.
Por que o meu script não consegue ler um header de resposta personalizado?
Por padrão, um script de outra origem só enxerga uma lista curta de headers de resposta seguros. Liste os demais em Access-Control-Expose-Headers, por exemplo X-Request-Id, e eles passam a ser legíveis.
Mesma origem, e por que o navegador bloqueia leituras
Uma origem é o esquema, o host e a porta juntos. https://app.example.com e https://api.example.com são origens diferentes, assim como as versões http e https de um mesmo domínio. Por padrão, um script pode enviar uma requisição para outra origem, mas não pode ler o que volta.
O CORS é a autorização explícita. A API lista em quais origens confia e o navegador aplica essa lista em nome do visitante. É por isso que o header precisa vir do servidor que você chama.
Requisições simples e preflights
Um GET comum, ou um POST de um formulário normal, sai direto com um header Origin. Em seguida o navegador confere se a resposta traz uma origem permitida antes de entregá-la ao seu código.
Qualquer coisa além disso envia antes uma requisição OPTIONS com Access-Control-Request-Method e Access-Control-Request-Headers. A requisição real só sai se o servidor aprovar os dois. Access-Control-Max-Age permite que o navegador lembre a aprovação, embora os navegadores limitem por quanto tempo.
Como ler o erro no console
Por segurança, o seu JavaScript só fica sabendo que o fetch falhou, com um TypeError sem detalhes. O motivo real aparece no console do navegador e indica o header que estava ausente ou errado. Compare-o com a tabela de mensagens abaixo.
Erros comuns de CORS e como corrigi-los
O texto das mensagens varia de navegador para navegador. Estes são os motivos como o Firefox os nomeia no MDN; o Chrome diz o mesmo em frases mais longas.
Motivos de CORS no console e a correção no servidor
Motivo no console
O que significa
Correção no servidor
CORS header 'Access-Control-Allow-Origin' missing
A resposta não trouxe nenhum header allow-origin
Adicione o header a todas as respostas, inclusive as de erro
CORS header 'Access-Control-Allow-Origin' does not match
O header indica outra origem
Devolva a origem de quem chama a partir da sua lista de permissões
Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*'
Cookies foram enviados, mas o servidor respondeu com *
Devolva a origem exata e Allow-Credentials: true
expected 'true' in CORS header 'Access-Control-Allow-Credentials'
Credenciais enviadas, mas a flag está ausente
Envie Access-Control-Allow-Credentials: true
Did not find method in CORS header 'Access-Control-Allow-Methods'
O preflight não aprovou o método
Liste PUT, PATCH ou DELETE em Allow-Methods
missing token in CORS header 'Access-Control-Allow-Headers' from CORS preflight channel
Um header de requisição como Authorization não foi aprovado
Liste-o em Allow-Headers
CORS preflight channel did not succeed
O OPTIONS falhou, redirecionou ou devolveu um erro
Responda ao OPTIONS com 204 e os headers
Multiple CORS header 'Access-Control-Allow-Origin' not allowed
Duas camadas (app e proxy) adicionaram o header
Defina-o em um único lugar
Trechos de servidor
Express: lista de permissões com credenciais
Devolve a origem só quando ela está na lista, responde aos preflights com 204 e adiciona Vary para que os caches mantenham as origens separadas.
Route handler do Next.js
Um endpoint público e sem cookies pode usar o curinga. Exporte OPTIONS para que o preflight tenha a sua própria resposta.
nginx na frente de uma API
A flag always mantém o header também nas respostas de erro, então um 500 continua mostrando o status real em vez de um erro de CORS.
O lado do navegador: enviando cookies entre origens
credentials: "include" é o que ativa as regras de credenciais mais rígidas no servidor.
Simulador de requisições CORS
Descreva uma requisição e os headers de CORS do servidor para ver se um preflight é enviado e se o navegador deixa o seu script ler a resposta.
A requisição
A resposta do servidor
Origem da página
Origem da API
Método
Content-Type
Sem corpo
Header extra da requisição (em branco para nenhum)
Enviar cookies (credentials: include)
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Allow-Credentials: true
Veredito
Mesma origem: o CORS não se aplica, a resposta pode ser lida.
Sem preflight: esta é uma requisição simples.
Preflight necessário: o navegador envia OPTIONS antes.
Motivo: o método não é GET, HEAD nem POST.
Motivo: o header {v} não está na lista segura.
Motivo: o Content-Type {v} não é um dos três tipos simples.