CORS libera o navegador de outro site. Não é a trava do seu servidor
Entenda o que CORS faz de verdade, configure uma lista explícita de origens com credenciais e mantenha a autenticação e a autorização no servidor, onde elas valem para qualquer cliente.
O front em app.exemplo.com.br não conseguia chamar a API em api.exemplo.com.br. Alguém achou na internet a configuração que "resolve CORS", que devolve no cabeçalho a mesma origem que chegou e libera credenciais. Funcionou. Também passou a permitir que qualquer site, aberto no navegador de uma pessoa logada, leia os dados dela na sua API. A resposta curta: CORS não protege o seu servidor. É uma regra que o navegador aplica para decidir se um script de outro site pode ler a resposta. Configure uma lista explícita de origens, nunca reflita a origem recebida quando houver cookie, e mantenha autenticação e autorização em cada rota, porque clientes fora do navegador ignoram CORS completamente.
O que o CORS realmente faz
Navegadores aplicam a política de mesma origem: um script carregado de site-a.example pode até disparar pedidos para site-b.example, mas não pode ler a resposta. Origem é a combinação de esquema, host e porta. https://app.exemplo.com.br e https://api.exemplo.com.br são origens diferentes.
CORS é o mecanismo pelo qual o servidor afrouxa essa política. Ele responde com Access-Control-Allow-Origin dizendo qual origem pode ler. Se o script quiser mandar cookies junto (credentials: 'include' no fetch), o servidor também precisa responder Access-Control-Allow-Credentials: true, e aí a origem precisa ser exata: o navegador recusa * com credenciais.
Repare no que isso significa. Com CORS restrito, o pedido pode chegar ao seu servidor do mesmo jeito. Pedidos considerados simples, como um POST de formulário, saem sem consulta prévia; o navegador só esconde a resposta do script. Pedidos não simples, como um PUT com JSON, passam antes por uma consulta OPTIONS (o preflight), e o navegador só envia o pedido real se a resposta autorizar. Em nenhum caso o CORS impede o curl, um script Python ou um app de celular de chamar a sua API com o que quiserem.
Então o CORS protege o usuário, não o servidor. Ele impede que um site malicioso, rodando no navegador de alguém logado no seu sistema, leia as respostas da sua API com o cookie dessa pessoa. Liberar errado desfaz justamente essa proteção.
Por que refletir a origem é perigoso
A configuração "que resolve" lê o cabeçalho Origin do pedido e devolve o mesmo valor em Access-Control-Allow-Origin, com Allow-Credentials: true. Para o navegador, isso equivale a dizer: qualquer site pode ler as respostas autenticadas desta API. Uma página qualquer, visitada pela pessoa logada, faz fetch('https://api.exemplo.com.br/perfil', { credentials: 'include' }) e recebe nome, e-mail, endereço, o que a rota devolver.
O mesmo vale para validações frouxas: expressão regular que aceita qualquer coisa terminando em exemplo.com.br aceita golpeexemplo.com.br. Liberar a origem null também é arriscado, porque iframes com sandbox e alguns redirecionamentos produzem essa origem, e um atacante consegue gerá-la.
Como configurar CORS com lista explícita
- Liste as origens que realmente consomem a API pelo navegador, com esquema e sem barra no final.
- Compare a origem recebida por igualdade exata com essa lista.
- Só envie
Access-Control-Allow-Credentials: truese o front de fato usa cookie na API. - Se o valor de
Access-Control-Allow-Originmuda conforme o pedido, envieVary: Origin, para que caches não sirvam a resposta de uma origem para outra. - Restrinja métodos e cabeçalhos ao que o front usa.
- Mantenha em toda rota a verificação de sessão e de permissão, como se o CORS não existisse.
Em Express com o pacote cors:
const express = require('express');
const cors = require('cors');
const app = express();
const ORIGENS = new Set([
'https://app.exemplo.com.br',
'https://admin.exemplo.com.br'
]);
app.use(cors({
origin: function (origem, callback) {
callback(null, Boolean(origem) && ORIGENS.has(origem));
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
app.get('/perfil', exigirLogin, (req, res) => {
res.json(buscarPerfil(req.usuario.sub));
});
Quando a função devolve false, o pacote não envia os cabeçalhos de CORS. O pedido ainda é processado pela rota; quem bloqueia a leitura é o navegador. Por isso o exigirLogin continua ali. Evite origin: true nesse pacote: ele reflete qualquer origem recebida.
Para testar sem navegador, simule uma origem estranha e veja o que volta:
curl -s -D - -o /dev/null -H "Origin: https://site-qualquer.example" https://api.exemplo.com.br/perfil
Se a resposta trouxer Access-Control-Allow-Origin: https://site-qualquer.example, a API está refletindo a origem. Repita com a sua origem legítima para confirmar que ela aparece. Se a API está atrás de Nginx ou de um CDN, confira se alguma dessas camadas também adiciona cabeçalhos de CORS: duas camadas configurando o mesmo cabeçalho costumam gerar valores duplicados, que o navegador recusa, ou uma regra permissiva escondida que ninguém lembra de ter criado.
CORS, cookie e CSRF
Como o CORS não impede o envio de pedidos simples, ele não substitui a proteção contra CSRF. Um formulário em outro site ainda consegue fazer POST na sua API; o que segura esse pedido é o cookie com SameSite, como explicado em cookie de sessão com HttpOnly, e token anti-CSRF nas ações sensíveis. Exigir Content-Type: application/json ajuda, porque força o preflight, mas não é motivo para dispensar o resto.
Erros comuns
- Refletir a origem com credenciais. Qualquer site lê os dados da pessoa logada na sua API.
- Validar origem com regex ou
endsWith. Domínios registrados por terceiros com o seu nome no final passam. - Liberar a origem
null. Um atacante consegue produzir essa origem com iframe em sandbox. - Achar que CORS bloqueia o pedido. A ação já aconteceu no servidor quando o navegador esconde a resposta.
- Tirar autenticação de rota porque "só o nosso front chama". Qualquer cliente fora do navegador chama, e CORS não participa.
- Esquecer
Vary: Originem configuração manual. Um cache entrega a resposta com a origem errada e o front quebra de forma intermitente.
Checklist
- A lista de origens permitidas está escrita no código ou na configuração, uma a uma.
- O teste com
curle origem estranha não devolveAccess-Control-Allow-Origincom ela. - A origem
nullnão é aceita. Allow-Credentialssó está ligado se o front usa cookie.- Toda rota confere sessão e permissão por conta própria.
- Ações que mudam dados têm proteção contra CSRF além do CORS.
Rode o curl acima contra a sua API de produção, trocando o domínio. Qual origem, além das suas, recebe permissão de ler resposta com cookie?
Referência: https://developer.mozilla.org/docs/Web/HTTP/CORS