Todos nós já passámos por isso: você envia uma solicitação de API, espera pela resposta e, de repente, recebe uma mensagem de« erroCORS» no console do seu navegador.
Para muitos programadores, o primeiro instinto é encontrar uma solução rápida: adicionar Access-Control-Allow-Origin: * e seguir em frente. No entanto, essa abordagem falha completamente o objetivo. O CORS não é apenas mais um obstáculo de configuração, mas um dos mecanismos de segurança de navegador mais importantes já criados.
O CORS, ou Cross-Origin Resource Sharing (Partilha de Recursos entre Origens), existe para proteger os utilizadores, permitindo ao mesmo tempo a comunicação legítima entre domínios entre aplicações web. No entanto, é frequentemente mal compreendido, mal configurado ou tratado como um bug a ser «contornado».
Mas agora não mais.
Neste guia, iremos além do básico. Irá aprender:
- Por que o CORS existe e como ele evoluiu a partir da Política de Mesma Origem (SOP)
- Como os navegadores e servidores realmente negociam o acesso entre origens
- O que faz com que algumas configurações CORS falhem, mesmo quando «parecem corretas»
- Como lidar com solicitações pré-voo, credenciais e peculiaridades do navegador com segurança
No final, não só saberá como configurar o CORS, mas também compreenderá por que ele se comporta dessa maneira e como projetar as suas APIs com segurança em torno dele.
O que é CORS (e por que existe)
CORS é um padrão de segurança do navegador que define como as aplicações web de uma origem podem aceder com segurança aos recursos de outra.
Para entender a segurança CORS, primeiro é preciso saber por que ela foi criada.
Muito antes das APIs e dos microsserviços dominarem a web, os navegadores seguiam uma regra simples chamada Política de Mesma Origem (SOP).
Essa política determinava que uma página da Web só poderia enviar e receber dados da mesma origem, ou seja, do mesmo protocolo, domínio e porta.
Por exemplo:
Essa restrição fazia todo o sentido nos primórdios da web, quando a maioria dos sites era monolítica. Um único site hospedava seu front-end, back-end e ativos, tudo sob um único domínio.
Mas, à medida que a web evoluiu com APIs, microsserviços e integrações de terceiros, essa mesma regra tornou-se uma barreira. Os programadores precisavam de aplicações front-end para comunicar com outros domínios, tais como:
- www.example.com a comunicar com api.example.com
- A sua aplicação a ligar-se a um CDN ou a um ponto final de análise
- Clientes web que chamam APIs de terceiros (como Stripe ou Google Maps)
A Política de Mesma Origem tornou-se uma barreira que bloqueava as arquiteturas modernas e distribuídas.
Foi aí que entrou em cena o Cross-Origin Resource Sharing (CORS).
Em vez de remover completamente as restrições do navegador, o CORS introduziu uma flexibilização controlada do SOP. Ele criou uma maneira segura para navegadores e servidores se comunicarem entre domínios, com segurança e somente quando ambos os lados concordam.
Pense nisso desta forma: SOP é uma porta trancada que não deixa ninguém entrar, e CORS é a mesma porta, mas com uma lista de convidados e um segurança verificando as identidades.
Este equilíbrio entre flexibilidade e proteção é o que torna a configuração CORS fundamental para todas as aplicações web modernas.
Entendendo a Política de Mesma Origem (SOP)
Antes de aprofundarmos a configuração do CORS, é essencial compreender a sua base: a Política de Mesma Origem (SOP).
Como mencionado anteriormente, o SOP é a primeira linha de defesa do navegador contra comportamentos maliciosos na web. Ele impede que um site acesse livremente os dados de outro, o que poderia expor informações confidenciais, como cookies, tokens de autenticação ou detalhes pessoais.
Veja como funciona na prática: quando uma página da Web é carregada no seu navegador, ela recebe uma origem atribuída com base em três elementos: o protocolo, o host e a porta:
https:// api.example.com :443
^ ^ ^
protocolo host porta
Dois URLs são considerados da mesma origem apenas se todas essas três partes corresponderem. Caso contrário, o navegador trata-os como de origens diferentes.
Esta regra simples impede ações prejudiciais entre sites. Sem ela, um site aleatório poderia carregar o seu painel de controle bancário online em um quadro invisível, ler o seu saldo e enviá-lo a um invasor, tudo sem o seu consentimento.
Em suma, o SOP existe para isolar o conteúdo entre diferentes sites, garantindo que cada origem seja uma zona de segurança independente.
Por que o SOP por si só não era suficiente
A Política de Mesma Origem funcionava perfeitamente quando os sites eram independentes. Mas, à medida que a web evoluiu para um ecossistema de APIs, microsserviços e arquiteturas distribuídas, essa regra rígida tornou-se uma grande limitação.
As aplicações modernas precisavam de:
- Chamar as suas próprias APIs hospedadas em diferentes subdomínios (app.example.com → api.example.com)
- Obter recursos de CDNs ou serviços de terceiros
- Integre com APIs externas como Stripe, Firebase ou Google Maps
De acordo com o SOP, essas solicitações legítimas de origem cruzada foram bloqueadas. Os programadores tentaram todas as soluções alternativas possíveis, incluindo JSONP, proxies reversos ou domínios duplicados, mas essas correções eram inseguras ou extremamente complexas.
Foi aí que o CORS (Cross-Origin Resource Sharing) mudou o jogo.
O CORS introduziu um sistema de handshake que permitiu que navegadores e servidores negociassem a confiança. Em vez de quebrar o SOP, ele o ampliou, fornecendo uma maneira segura de colocar origens específicas na lista de permissões para comunicação entre domínios.
Como funciona o CORS: o fluxo ao nível do protocolo
Como mencionado anteriormente, quando o seu navegador faz uma solicitação para uma origem diferente, ele não a envia cegamente. Em vez disso, ele segue um protocolo CORS bem definido: uma conversa de ida e volta entre o navegador e o servidor para determinar se a solicitação deve ser permitida.
Basicamente, o CORS funciona através de cabeçalhos HTTP. O navegador anexa um cabeçalho Origin a cada solicitação de origem cruzada, informando ao servidor de onde a solicitação está a vir. O servidor então responde com um ou mais cabeçalhos Access-Control-* que definem o que é permitido.
Aqui está um exemplo simplificado dessa conversa:
# Request
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
# Response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"message": "Success"}
Neste caso, o servidor permite explicitamente que a origem https://app.example.com aceda ao seu recurso. O navegador verifica esta resposta, confirma a correspondência e entrega os dados ao seu JavaScript.
Mas se as origens não corresponderem ou se os cabeçalhos de resposta estiverem em falta ou incorretos, o navegador bloqueia silenciosamente a resposta. Não verá os dados, apenas aquela mensagem frustrante de «erro CORS» na sua consola.
É importante observar que o CORS, por si só, não torna um servidor mais seguro. Em vez disso, ele impõe regras de interação entre navegadores e servidores, uma camada de segurança que garante que apenas origens confiáveis possam aceder a recursos protegidos.
Tipos de solicitações CORS
O CORS define dois tipos principais de solicitações: simples e pré-verificadas. A diferença está na quantidade de verificações que o navegador realiza antes de enviar os dados.
1. Pedidos simples
Uma solicitação simples é o tipo mais direto. Ela é automaticamente permitida pelos navegadores, desde que siga regras específicas:
- Utiliza um destes métodos: GET, HEAD ou POST
- Inclui apenas determinados cabeçalhos:
- Aceitar
- Aceitar idioma
- Idioma do conteúdo
- Tipo de conteúdo (mas apenas application/x-www-form-urlencoded, multipart/form-data ou text/plain)
- Não usa cabeçalhos ou fluxos personalizados
Eis como se apresenta:
# Request
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
# Response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
{"message": "This is the response data"}Neste caso:
- O navegador adiciona automaticamente o cabeçalho Origin.
- O servidor deve retornar Access-Control-Allow-Origin com uma origem correspondente.
- Se a origem não corresponder ou estiver em falta, o navegador bloqueia a resposta.
2. Pedidos de pré-voo
As coisas ficam mais interessantes com pedidos não simples. Por exemplo, quando utiliza métodos como PUT, DELETE ou cabeçalhos personalizados, como Authorization.
Antes de enviar a solicitação propriamente dita, o navegador realiza uma verificação prévia usando uma solicitação OPTIONS. Essa etapa garante que o servidor permita explicitamente a operação pretendida.
Aqui está um exemplo:
# Preflight Request
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, authorization
# Preflight Response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: PUT, POST, GET, DELETE
Access-Control-Allow-Headers: content-type, authorization
Access-Control-Max-Age: 3600
# Actual Request (only sent if preflight succeeds)
PUT /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer token123
{"data": "update this resource"}Nesta sequência:
- O navegador deteta um pedido não simples.
- Ele envia uma solicitação OPTIONS pré-voo, pedindo permissão para o método real e os cabeçalhos.
- O servidor responde com os métodos, cabeçalhos e origens que permite.
- Se a verificação pré-voo for aprovada, o navegador envia a solicitação real. Caso contrário, ele a bloqueia.
Tratamento de credenciais em CORS
Ao lidar com APIs que exigem autenticação, como cookies, tokens ou logins baseados em sessão, o CORS se comporta de maneira diferente.
Por predefinição, os navegadores tratam as solicitações entre origens como não autenticadas por motivos de segurança. Isso significa que cookies ou cabeçalhos de autenticação HTTP não são incluídos automaticamente.
Para ativar as solicitações credenciadas com segurança, duas etapas importantes devem estar alinhadas:
1. O cliente deve permitir explicitamente as credenciais:
fetch('https://api.example.com/data', {
credentials: 'include'
})2. O servidor deve permitir explicitamente:
Controlo de acesso - Permitir credenciais: verdadeiro
Mas há um senão, e é um grande senão.
Quando Access-Control-Allow-Credentials está definido como verdadeiro, não é possível usar um curinga (*) em Access-Control-Allow-Origin. Os navegadores rejeitarão a resposta se você tentar.
Isso porque permitir que todas as origens enviem pedidos com credenciais iria contra todo o propósito da segurança CORS, pois permitiria que qualquer site na Internet acedesse a dados privados vinculados à sessão de um utilizador.
Então, em vez disso:
Controlo de acesso - Permitir origem: *
Controlo de acesso - Permitir credenciais: verdadeiroDeve sempre utilizar uma origem específica:
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Credentials: trueSe a sua API atende a vários domínios confiáveis, pode retornar dinamicamente o cabeçalho de origem correto no lado do servidor:
const allowedOrigins = ['https://app1.com', 'https://app2.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}Essa abordagem garante que as suas solicitações autenticadas permaneçam seguras e intencionais, não abertas a qualquer pessoa que tente acessá-las.
Como os navegadores determinam quais solicitações são elegíveis para CORS
Antes mesmo de uma solicitação chegar ao seu servidor, o navegador decide se ela se enquadra nas regras CORS.
Esta decisão depende da origem do pedido e se este tem como alvo outro domínio, porta ou protocolo.
Por exemplo:
- Solicitando https://api.example.com a partir de uma página servida em https://example.com: ✅ CORS aplica-se (subdomínio diferente).
- Solicitando https://example.com:3000 a partir de https://example.com: ✅ CORS aplica-se (porta diferente).
- Solicitando https://example.com a partir do mesmo domínio e porta: ❌ CORS não se aplica.
Se o navegador detetar que um pedido atravessa origens, inclui automaticamente o cabeçalho Origin no pedido:
Origem: https://example.com
Este cabeçalho informa ao servidor de onde veio a solicitação e é o que o servidor usa para decidir se permite ou bloqueia o acesso.
Se a resposta não tiver os cabeçalhos corretos (como Access-Control-Allow-Origin), o navegador simplesmente bloqueia o acesso à resposta, mesmo que o servidor tenha tecnicamente enviado uma.
Essa é uma distinção importante: o navegador aplica o CORS, não o servidor.
Verificações de segurança interna, XMLHttpRequest vs Fetch e diferenças entre navegadores
Nem todos os navegadores lidam com CORS da mesma forma, mas todos seguem o mesmo modelo de segurança: nunca confiar em dados de origem cruzada, a menos que explicitamente permitido.
O que difere é o rigor com que aplicam as regras e a quais APIs as aplicam.
1. A verificação de segurança interna CORS
Quando um navegador recebe uma resposta a uma solicitação de origem cruzada, ele executa uma etapa de validação interna antes de expor a resposta ao seu código JavaScript.
Verifica cabeçalhos como:
- Access-Control-Allow-Origin: deve corresponder à origem da solicitação (ou ser * em alguns casos).
- Access-Control-Allow-Credentials: deve ser verdadeiro se cookies ou tokens de autenticação estiverem envolvidos.
- Access-Control-Allow-Methods e Access-Control-Allow-Headers: devem corresponder à solicitação pré-voo original, caso tenha sido enviada.
Se alguma dessas verificações falhar, o navegador não gera um erro HTTP, simplesmente bloqueia o acesso à resposta e registra um erro CORS no console.
Isso torna a depuração complicada, porque a solicitação de rede real ainda foi bem-sucedida, mas o navegador oculta o resultado por segurança.
2. XMLHttpRequest vs Fetch
Tanto o XMLHttpRequest quanto a moderna API fetch() suportam CORS, mas eles se comportam de maneira ligeiramente diferente quando se trata de credenciais e padrões.
Com XMLHttpRequest:
- Os cookies e a autenticação HTTP são enviados automaticamente se withCredentials estiver definido como verdadeiro.
- O comportamento pré-voo depende da adição de cabeçalhos personalizados.
Com o Fetch:
- As credenciais (cookies, autenticação HTTP) não estão incluídas por predefinição.
- Deve ativá-los explicitamente usando:
fetch("https://api.example.com/data", {
credentials: "include"
});- O fetch também trata os redirecionamentos de forma mais rigorosa sob CORS, pois não seguirá redirecionamentos entre origens, a menos que seja permitido.
Portanto, embora o fetch seja mais limpo e moderno, ele também é menos tolerante quando se esquece um cabeçalho ou perde uma regra de credencial.
3. Diferenças e peculiaridades dos navegadores
Embora a especificação CORS seja padrão, os navegadores implementam-na com diferenças subtis:
- O Safari pode ser excessivamente rigoroso com cookies e solicitações de credenciais, especialmente quando cookies de terceiros são bloqueados.
- O Firefox, por vezes, armazena em cache respostas de pré-verificação com falha por mais tempo do que o esperado, levando a resultados inconsistentes durante os testes.
- O Chrome aplica o CORS em determinadas cadeias de redirecionamento de forma mais agressiva do que em outras.
Devido a essas diferenças, uma configuração que funciona perfeitamente num navegador pode falhar silenciosamente noutro.
É por isso que é fundamental testar as configurações CORS em todos os navegadores, especialmente quando credenciais ou redirecionamentos estão envolvidos.
Tratamento do cabeçalho Origin no lado do servidor
Embora o navegador imponha o CORS, a decisão real é tomada no servidor.
Quando o navegador envia uma solicitação de origem cruzada, ele sempre inclui o cabeçalho Origin. A função do servidor é inspecionar esse cabeçalho, decidir se deve permitir a solicitação e retornar os cabeçalhos CORS corretos em resposta.
1. Validação da origem
Um pedido típico pode ser assim: Origem: https://frontend.example.com
No servidor, o seu código precisa verificar se essa origem é permitida. A abordagem mais simples (e segura) é manter uma lista de domínios confiáveis:
const allowedOrigins = ["https://frontend.example.com", "https://admin.example.com"];
if (allowedOrigins.includes(req.headers.origin)) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
}Isso garante que apenas clientes conhecidos possam aceder à sua API, enquanto outros não recebem permissão CORS.
Evite retornar Access-Control-Allow-Origin: * se a sua API lida com cookies, tokens ou outras credenciais.
2. Tratamento de pedidos de pré-voo
Para pedidos de pré-verificação OPTIONS, o servidor deve responder com o mesmo cuidado que para os pedidos principais.
Uma resposta de pré-verificação completa inclui:
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Esses cabeçalhos informam ao navegador o que é permitido e por quanto tempo ele pode armazenar essa decisão em cache. Se algum deles estiver ausente ou incorreto, o navegador bloqueará a solicitação subsequente, mesmo que o seu endpoint funcione corretamente.
3. Definição dinâmica de cabeçalhos CORS
Em sistemas de grande porte (como plataformas multitenant ou APIs com vários clientes), as origens permitidas podem precisar ser dinâmicas.
Por exemplo:
const origin = req.headers.origin;
if (origin && origin.endsWith(".trustedclient.com")) {
res.setHeader("Access-Control-Allow-Origin", origin);
}Este padrão permite todos os subdomínios de um domínio confiável, ao mesmo tempo em que filtra fontes desconhecidas.
Certifique-se apenas de validar cuidadosamente as origens e não fazer correspondências de cadeias de caracteres nas entradas do utilizador sem restrições, ou os invasores poderão falsificar cabeçalhos que parecem válidos.
4. Por que «funciona no Postman» não significa que está configurado corretamente
Um dos maiores equívocos sobre CORS é este: «Funciona no Postman, então deve ser um problema do navegador».
O Postman não aplica CORS de forma alguma, porque não é um navegador.
Isso significa que mesmo uma API completamente aberta, sem cabeçalhos Access-Control-*, responderá normalmente nesse navegador, mas falhará imediatamente no Chrome ou no Firefox.
Portanto, se a sua API funciona no Postman, mas não na sua aplicação web, é provável que os seus cabeçalhos CORS estejam incompletos ou mal configurados.
Configurações incorretas comuns do CORS (e como evitá-las)
1. Usando Access-Control-Allow-Origin: * com credenciais
Este é o erro mais frequente e perigoso.
Se a sua resposta incluir ambos:
Controlo de acesso - Permitir origem: *
Controlo de acesso - Permitir credenciais: verdadeiro... o navegador bloqueará a solicitação automaticamente.
A especificação CORS proíbe o uso de caracteres curinga quando credenciais estão incluídas, pois isso permitiria que qualquer site acesse dados do utilizador vinculados a cookies ou tokens de autenticação.
Correção: sempre retorne uma origem específica quando credenciais forem usadas:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true2. Esquecer de lidar com pedidos de pré-voo
Muitas APIs respondem corretamente a GET e POST, mas esquecem a solicitação prévia OPTIONS.
Quando isso acontece, o navegador nunca chega ao seu ponto final real e bloqueia a solicitação principal após a falha na pré-verificação.
Correção: trate explicitamente as solicitações OPTIONS e responda com os cabeçalhos corretos:
if (req.method === "OPTIONS") {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
return res.sendStatus(204);
}3. Cabeçalhos de solicitação e resposta desalinhados
Outra questão sutil: a sua solicitação pré-voo pode pedir determinados cabeçalhos, mas o servidor não os permite explicitamente.
Por exemplo, se o seu pedido incluir:
Cabeçalhos de solicitação de controlo de acesso: Autorização, Tipo de conteúdo
... mas o servidor apenas responde com:
Controlo de acesso - Permitir cabeçalhos: Tipo de conteúdo
... o navegador bloqueia-o. Ambas as listas devem corresponder exatamente.
Solução: Certifique-se de que o Access-Control-Allow-Headers inclui todos os cabeçalhos que o cliente pode enviar, especialmente Authorization, Accept e cabeçalhos personalizados.
4. Devolvendo vários cabeçalhos Access-Control-Allow-Origin
Alguns proxies ou frameworks mal configurados enviam vários cabeçalhos Access-Control-Allow-Origin (por exemplo, uma origem estática * e uma dinâmica).
Os navegadores consideram isso inválido e bloqueiam totalmente a solicitação.
Correção: Sempre retorne um único cabeçalho Access-Control-Allow-Origin válido.
5. Esquecer as restrições do método
Se não incluir todos os métodos permitidos em Access-Control-Allow-Methods, os navegadores rejeitarão pedidos legítimos.
Por exemplo, uma API pode suportar PUT, mas a sua resposta pré-voo só permite GET e POST.
Solução: liste todos os métodos suportados ou combine dinamicamente as suas rotas de API para garantir a consistência.
6. Ignorar respostas pré-voo armazenadas em cache
Os navegadores modernos armazenam em cache os resultados da pré-verificação para melhorar o desempenho.
Mas se o seu servidor ou CDN armazenar em cache as respostas sem variar por origem, poderá enviar acidentalmente os cabeçalhos CORS errados para outro cliente.
Correção: Use o cabeçalho Vary: Origin para garantir que as respostas sejam armazenadas em cache separadamente por origem.
Os problemas de CORS raramente resultam de um grande erro. Normalmente, são o resultado de várias pequenas incompatibilidades entre as expectativas do navegador e a configuração do servidor. Compreender esses padrões ajuda a evitar ciclos intermináveis de depuração de «erros de CORS».
O CORS não é o inimigo, mas sim o mal-entendido sobre ele
À primeira vista, o CORS parece uma barreira desnecessária, ou mais como um guardião que interrompe as suas solicitações e retarda o desenvolvimento.
Mas, na realidade, é um dos recursos de segurança de navegador mais importantes já criados.
Depois de entender como funciona, você deixa de ver os «erros CORS» como falhas aleatórias e passa a vê-los como sinais de que o seu cliente e servidor precisam de se alinhar melhor em termos de confiança, cabeçalhos ou credenciais.
Quer esteja a criar uma aplicação de página única ou um ecossistema de API distribuído, o CORS é o seu aliado para manter os utilizadores seguros, ao mesmo tempo que permite uma comunicação segura entre domínios.
Portanto, da próxima vez que você se deparar com aquela mensagem familiar do console, não recorra ao curinga. Leia os cabeçalhos, trace a lógica e deixe que o seu entendimento, e não um hack aleatório, guie a correção!
Proteja seu software agora



.avif)
