DEV Community

Cover image for Como Corrigir Erros CORS: Depurando Access-Control-Allow-Origin
Lucas
Lucas

Posted on Originally published at apidog.com

Como Corrigir Erros CORS: Depurando Access-Control-Allow-Origin

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.

Experimente o Apidog hoje

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, curl e 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, HEAD ou POST.
  • Envia cabeçalhos personalizados, como Authorization.
  • Usa um Content-Type como application/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
Enter fullscreen mode Exit fullscreen mode

O navegador está perguntando:

Uma página em app.example.com quer fazer um POST com 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

mas o servidor responde:

Access-Control-Allow-Origin: *
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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ão OPTIONS retorna 404 ou 405.
  • 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);
});
Enter fullscreen mode Exit fullscreen mode

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 http e https.
  • 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');
}
Enter fullscreen mode Exit fullscreen mode

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 authorization não é permitido por Access-Control-Allow-Headers.
  • O método PUT não é permitido por Access-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
Enter fullscreen mode Exit fullscreen mode

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 http redirecionando para https.
  • Barra final adicionada automaticamente pelo framework.
  • Gateway redirecionando /v1/orders para /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
}));
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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: *, *
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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 500 indica que o problema não é CORS.
  2. Teste o preflight manualmente. Crie uma requisição OPTIONS com estes cabeçalhos:
   Origin: https://app.example.com
   Access-Control-Request-Method: POST
   Access-Control-Request-Headers: authorization, content-type
Enter fullscreen mode Exit fullscreen mode
  1. Inspecione a resposta. Procure por Access-Control-Allow-Origin, Access-Control-Allow-Methods e Access-Control-Allow-Headers. Compare cada valor com o que o frontend precisa.
  2. Verifique a correção. Depois de alterar o servidor, reenvie a mesma requisição OPTIONS salva 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 *.
  • OPTIONS retorna 2xx?
  • Os métodos e cabeçalhos permitidos cobrem a requisição real?
  • Existe algum redirecionamento na URL do preflight?
  • Respostas 401, 403 e 500 incluem 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)