Como corrigir erros CORS: preflight, cabeçalhos e exemplos práticos
Você lança um novo frontend, abre o console e encontra um erro CORS vermelho: a requisição foi “bloqueada pela política CORS”. Sua API funciona no Apidog ou no curl, mas o navegador se recusa a entregar a resposta ao JavaScript. Frustrante? Sim. Misterioso? Não, quando você sabe onde procurar.
O ponto central é: o erro CORS é imposto pelo navegador, mas causado pelo servidor. O navegador bloqueia a resposta porque o servidor não enviou os cabeçalhos Access-Control-Allow-Origin corretos. Por isso, a correção quase sempre está na configuração do servidor, não no frontend.
Este guia explica:
- O que é CORS e como ele funciona.
- Como funciona o preflight (
OPTIONS). - Os seis erros CORS mais comuns e suas correções.
- Configurações para Express, Spring Boot e Nginx.
- Como depurar CORS fora do navegador.
O que é um erro CORS?
CORS significa Cross-Origin Resource Sharing (Compartilhamento de Recursos de Origem Cruzada).
Por padrão, os navegadores aplicam a política de mesma origem. Assim, o JavaScript executado em https://app.example.com não pode ler diretamente uma resposta de https://api.example.com, porque o esquema, o host ou a porta são diferentes.
CORS é o mecanismo que permite ao servidor relaxar essa regra de forma intencional. Consulte a documentação CORS do MDN e a especificação Fetch para detalhes completos.
Três pontos resolvem a maioria das confusões:
-
O navegador impõe: apenas navegadores aplicam as verificações CORS. Chamadas entre servidores,
curle clientes de API desktop ignoram completamente essas verificações. - O servidor configura: o navegador decide com base nos cabeçalhos de resposta enviados pelo servidor. Sem cabeçalhos, não há acesso ao JavaScript.
- A requisição pode chegar ao servidor: em requisições simples, o servidor processa e responde normalmente. O navegador apenas impede que o JavaScript leia a resposta.
CORS não é uma barreira de segurança em torno da API. Ele protege usuários contra páginas maliciosas que tentam ler dados de outra origem usando, por exemplo, os cookies da vítima.
Ao encontrar um erro CORS, não comece procurando uma solução alternativa no frontend. Leia a mensagem e corrija o cabeçalho ausente ou incorreto no servidor.
Anatomia do preflight
Antes de certas requisições de origem cruzada, o navegador envia uma requisição OPTIONS chamada preflight (pré-checagem).
Ela costuma ser disparada quando a requisição:
- Usa métodos diferentes de
GET,HEADouPOST. - Envia cabeçalhos personalizados, como
Authorization. - Usa um
Content-Typecomoapplication/json.
Um preflight se parece com isto:
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
O navegador está perguntando:
Uma página em
app.example.comquer fazer umPOSTcom estes cabeçalhos. Isso é permitido?
Uma resposta correta do servidor seria:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
Se qualquer parte estiver faltando, o navegador cancela a requisição real antes de enviá-la. O endpoint nunca é executado, os logs mostram apenas o OPTIONS e o console exibe um erro CORS.
Access-Control-Max-Age informa por quanto tempo o navegador pode armazenar esse resultado em cache. Neste exemplo, o preflight pode ser reutilizado por 86.400 segundos.
A pergunta mais importante durante a depuração é:
O preflight falhou ou a requisição real falhou?
Os seis erros CORS mais comuns
1. Nenhum cabeçalho Access-Control-Allow-Origin está presente
O servidor respondeu sem nenhum cabeçalho CORS. Como o navegador não tinha uma permissão para avaliar, bloqueou o acesso.
Configure o servidor para enviar a origem solicitante ou * para APIs públicas e sem credenciais:
Access-Control-Allow-Origin: https://app.example.com
Uma armadilha comum são as respostas de erro. Às vezes, o middleware adiciona cabeçalhos CORS apenas nas respostas 2xx. Se a API retornar 500, o navegador poderá exibir um erro CORS em vez do erro real.
Garanta que os cabeçalhos sejam enviados em todas as respostas, incluindo 401, 403 Forbidden e 500.
Veja também: 403 Forbidden.
2. Wildcard * não pode ser usado com credenciais
Essa mensagem aparece quando o frontend usa:
credentials: 'include'
mas o servidor responde:
Access-Control-Allow-Origin: *
A especificação Fetch proíbe essa combinação. Um wildcard com credenciais permitiria que qualquer site lesse respostas autenticadas.
Use a origem exata e habilite credenciais:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Valide a origem recebida contra uma lista de permissões antes de repeti-la. Refletir origens arbitrárias com credenciais habilitadas elimina a proteção.
3. A resposta ao preflight falha na verificação de controle de acesso
O servidor não tratou a requisição OPTIONS. Por exemplo:
- A rota aceita apenas
POST, entãoOPTIONSretorna404ou405. - O middleware de autenticação rejeita o preflight com
401. - O preflight não contém o token de autenticação, pois navegadores não enviam credenciais nessa etapa.
Trate OPTIONS explicitamente e retorne um status 2xx com os cabeçalhos CORS antes da autenticação:
app.options('/v1/orders', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type'
});
res.sendStatus(204);
});
Na maioria dos frameworks, montar o middleware CORS antes do middleware de autenticação resolve o problema.
4. O valor do cabeçalho não corresponde à origem
O servidor envia Access-Control-Allow-Origin, mas informa a origem errada.
Causas frequentes:
- Origem de produção definida enquanto você testa em
http://localhost:5173. - Diferença entre
httpehttps. - Porta diferente.
- Barra final indevida.
https://app.example.com/não é um valor de origem válido nesse contexto.
Compare a origem exatamente, repita apenas uma origem permitida e envie Vary: Origin:
const allowed = ['https://app.example.com', 'http://localhost:5173'];
if (allowed.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Vary', 'Origin');
}
O Vary: Origin evita que caches ou CDNs entreguem a resposta configurada para uma origem a outra origem.
5. Método ou cabeçalho da requisição não permitido
Exemplos de mensagens:
- O cabeçalho
authorizationnão é permitido porAccess-Control-Allow-Headers. - O método
PUTnão é permitido porAccess-Control-Allow-Methods.
Nesse caso, o preflight foi processado, mas a resposta não cobre tudo o que a requisição real precisa.
Inclua todos os métodos e cabeçalhos usados pelo frontend:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id
Nomes de cabeçalhos não diferenciam maiúsculas de minúsculas. Os métodos diferenciam e devem ser escritos em maiúsculas.
6. Redirecionamento não permitido para preflight
O preflight recebeu uma resposta 301 ou 302. Navegadores podem recusar redirecionamentos durante o preflight.
Causas típicas:
- URL
httpredirecionando parahttps. - Barra final adicionada automaticamente pelo framework.
- Gateway redirecionando
/v1/orderspara/v1/orders/.
Aponte o frontend diretamente para a URL final, use https desde o início e mantenha a convenção de barra final do roteador.
Confirme o comportamento com uma requisição OPTIONS manual e verifique se o endpoint retorna 2xx, não 3xx.
Exemplos de configuração
Express
Use o middleware cors oficial em vez de criar os cabeçalhos manualmente:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400
}));
Monte o middleware antes da autenticação para que os preflights não sejam rejeitados por falta de token.
Em aplicações Flask, a extensão Flask-CORS fornece o mesmo padrão de configuração.
Spring Boot
Configure o CORS globalmente com WebMvcConfigurer:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v1/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(86400);
}
}
Se estiver usando Spring Security, chame .cors(Customizer.withDefaults()) na cadeia de filtros. Caso contrário, a camada de segurança poderá bloquear o preflight antes que ele chegue à configuração MVC.
Consulte a documentação CORS do Spring para outras opções.
Nginx
Quando o Nginx fica na frente da aplicação, você pode responder aos preflights na borda:
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
A flag always é importante. Sem ela, o Nginx ignora add_header em respostas 4xx e 5xx, recriando o primeiro erro em todas as requisições com falha.
Escolha uma única camada para gerenciar CORS. Se o Nginx e a aplicação adicionarem os mesmos cabeçalhos, o navegador poderá receber valores duplicados, como:
Access-Control-Allow-Origin: *, *
Nesse caso, a resposta será rejeitada.
Depure CORS fora do navegador com Apidog
A mensagem do console informa que o navegador bloqueou algo, mas não mostra exatamente o que o servidor enviou. A maneira mais rápida de descobrir a causa é remover o navegador do teste.
O Apidog é um cliente de API desktop. Suas requisições não estão sujeitas às verificações CORS do navegador. Isso permite um diagnóstico direto:
- Se a requisição funcionar no Apidog, a lógica da API provavelmente está correta e faltam cabeçalhos CORS.
- Se ela falhar também no Apidog, há um problema comum na API que está sendo confundido com CORS.
Um fluxo de depuração eficiente:
-
Repita a requisição real. Copie a requisição com falha da aba Rede do navegador e recrie-a no Apidog com o mesmo método, cabeçalhos e corpo. Verifique o status e o corpo da resposta. Um
500indica que o problema não é CORS. -
Teste o preflight manualmente. Crie uma requisição
OPTIONScom estes cabeçalhos:
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
-
Inspecione a resposta. Procure por
Access-Control-Allow-Origin,Access-Control-Allow-MethodseAccess-Control-Allow-Headers. Compare cada valor com o que o frontend precisa. -
Verifique a correção. Depois de alterar o servidor, reenvie a mesma requisição
OPTIONSsalva e confirme os cabeçalhos atualizados.
Esse fluxo resolve rapidamente o clássico “funciona no meu cliente de API, mas falha no navegador”, incluindo dúvidas relacionadas ao teste CORS do Postman.
Clientes desktop funcionam porque ignoram CORS. O navegador falha quando o servidor não autoriza explicitamente a origem.
Para aprofundar seus testes, consulte o guia definitivo de testes de API e baixe o Apidog para salvar requisições OPTIONS junto aos testes dos endpoints.
Checklist CORS de 30 segundos
Antes de abrir um bug, confirme:
- A resposta com falha inclui
Access-Control-Allow-Origin? - O valor corresponde exatamente à origem da página: esquema, host e porta, sem barra final?
- Há cookies ou autenticação? Use uma origem específica e
Access-Control-Allow-Credentials: true, nunca*. -
OPTIONSretorna2xx? - Os métodos e cabeçalhos permitidos cobrem a requisição real?
- Existe algum redirecionamento na URL do preflight?
- Respostas
401,403e500incluem os mesmos cabeçalhos CORS das respostas de sucesso?
Na maioria dos casos, uma dessas verificações revela a causa. Teste o OPTIONS manualmente no Apidog, corrija a configuração do servidor e continue o desenvolvimento.
FAQ
Por que recebo um erro CORS apenas no navegador?
Porque apenas navegadores impõem CORS. A política de mesma origem protege usuários contra páginas maliciosas que tentam ler dados autenticados. curl, serviços backend e clientes desktop não aplicam essa regra.
Se a requisição funciona em todos os lugares, exceto no navegador, a API provavelmente está saudável, mas seus cabeçalhos CORS estão ausentes ou incorretos.
CORS se aplica ao Postman ou ao Apidog?
Não. Postman e Apidog são aplicativos desktop, não páginas web executadas em uma sandbox de navegador. Suas requisições ignoram completamente o CORS.
Isso os torna úteis para depuração: eles exibem os cabeçalhos brutos enviados pelo servidor. Uma requisição bem-sucedida em um cliente desktop não prova que o navegador funcionará, mas ajuda a isolar a camada com problema.
Um erro CORS é um recurso de segurança ou um bug?
É um recurso de segurança do navegador. O navegador impede que scripts leiam respostas de origem cruzada sem o consentimento do servidor.
Desativar CORS com flags ou extensões apenas esconde o problema na sua máquina. Os demais usuários continuarão enfrentando o erro. Corrija os cabeçalhos do servidor.
Posso usar Access-Control-Allow-Origin: * em todos os lugares?
Use * apenas para APIs públicas e somente leitura, sem cookies ou autenticação.
O wildcard é rejeitado quando credenciais são incluídas e indica que qualquer origem na web pode acessar os dados. Para APIs autenticadas, mantenha uma lista de origens permitidas, repita a origem correspondente e envie Vary: Origin.
Top comments (0)