CORS, ou Cross-Origin Resource Sharing, é o mecanismo usado pelos navegadores para decidir se um frontend pode acessar uma API hospedada em outra origem. Uma origem é formada por protocolo, domínio e porta. Alterar qualquer um desses elementos cria outra origem.
CORS não é autenticação e não impede chamadas feitas por servidores, aplicativos móveis, curl ou ferramentas automatizadas. Ele controla principalmente o acesso de scripts executados pelo navegador à resposta.
Instalação no Express
npm install corsimport express from 'express';
import cors from 'cors';
const app = express();
app.use(cors({
origin: 'https://app.example.com',
}));Evite usar origin: '*' por padrão em APIs privadas. Uma política aberta pode ser adequada para recursos públicos sem credenciais, mas deve ser uma decisão explícita.
Origem dinâmica
const allowedOrigins = new Set([
'https://app.example.com',
'https://admin.example.com',
]);
app.use(cors({
origin(origin, callback) {
if (!origin) {
callback(null, true);
return;
}
if (allowedOrigins.has(origin)) {
callback(null, true);
return;
}
callback(new Error('Origem não permitida'));
},
}));Requisições sem cabeçalho Origin podem vir de clientes não navegadores, health checks ou same-origin. Decida se elas devem ser aceitas com base na autenticação da API.
Credentials
Quando cookies ou autenticação do navegador precisam atravessar origens:
app.use(cors({
origin: 'https://app.example.com',
credentials: true,
}));O frontend também precisa usar credenciais:
await fetch('https://api.example.com/profile', {
credentials: 'include',
});Não é válido combinar credenciais com origem curinga. Além disso, cookies cross-site exigem atributos apropriados e proteção CSRF.
Preflight
O navegador envia uma requisição OPTIONS antes de certas chamadas, como métodos não simples, headers personalizados ou Content-Type específico.
OPTIONS /orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-typeA resposta precisa permitir o método e os headers:
app.use(cors({
origin: allowedOrigin,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'Idempotency-Key'],
maxAge: 600,
}));Preflight cache
Access-Control-Max-Age permite que o navegador reutilize a autorização por um período. Valores altos reduzem OPTIONS, mas atrasam mudanças de política. Comece com alguns minutos e valide compatibilidade.
Headers expostos
O navegador não entrega todos os headers da resposta ao JavaScript. Para expor valores úteis:
app.use(cors({
origin: allowedOrigin,
exposedHeaders: [
'ETag',
'Retry-After',
'RateLimit-Limit',
'RateLimit-Remaining',
],
}));Não exponha headers com tokens, cookies internos ou dados desnecessários.
CORS por rota
const publicCors = cors({ origin: '*' });
const privateCors = cors({
origin: ['https://app.example.com'],
credentials: true,
});
app.get('/public/catalog', publicCors, listCatalog);
app.get('/account', privateCors, requireAuth, getAccount);Separar políticas evita abrir a aplicação inteira para atender uma única rota pública.
Regex e subdomínios
Validar origem com endsWith('.example.com') pode aceitar domínios maliciosos como evil-example.com. Parseie a URL:
function isAllowed(origin) {
try {
const url = new URL(origin);
return url.protocol === 'https:' &&
(url.hostname === 'example.com' || url.hostname.endsWith('.example.com'));
} catch {
return false;
}
}Se usuários podem criar subdomínios, confiar em todos eles ainda pode ser inseguro.
Origem null
O valor Origin: null pode surgir em arquivos locais, iframes sandboxed e outros contextos. Não o permita automaticamente. Trate como uma origem específica e avalie o risco.
CORS e CSRF
CORS não substitui proteção CSRF. Um site malicioso pode enviar formulários e requisições simples mesmo sem ler a resposta. Se a autenticação usa cookies, implemente SameSite, tokens CSRF e validação de Origin ou Referer.
CORS e JWT
Um token no header Authorization não é enviado automaticamente pelo navegador, o que reduz alguns cenários de CSRF. Entretanto, XSS pode roubar tokens armazenados no frontend. Escolha armazenamento e políticas de segurança considerando o modelo de ameaça.
Proxy reverso
É comum configurar CORS tanto no proxy quanto no Node.js por engano. Isso pode gerar headers duplicados:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Origin: *Mantenha uma única fonte de verdade ou garanta regras consistentes entre camadas.
Cache e Vary
Quando a resposta muda conforme a origem, inclua Vary: Origin. O middleware costuma cuidar disso. Sem Vary, um CDN pode servir a política de uma origem para outra.
WebSockets
WebSocket não usa CORS da mesma forma, mas o handshake inclui Origin nos navegadores. Valide explicitamente a origem no servidor de WebSocket.
Server-Sent Events
SSE segue CORS porque usa HTTP. Se utilizar cookies cross-origin, configure credentials, origem específica e proteção contra CSRF em endpoints que alteram estado.
Erros no navegador
Quando CORS bloqueia, o JavaScript normalmente recebe um erro genérico. Abra a aba Network e verifique:
- Origin enviado;
- resposta OPTIONS;
- Allow-Origin;
- Allow-Methods;
- Allow-Headers;
- credentials;
- redirects;
- headers duplicados.
Testando com curl
curl -i \
-H 'Origin: https://app.example.com' \
https://api.example.com/profilePreflight:
curl -i -X OPTIONS \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: authorization,content-type' \
https://api.example.com/ordersTestes automatizados
const response = await request(app)
.get('/account')
.set('Origin', 'https://app.example.com');
expect(response.headers['access-control-allow-origin'])
.toBe('https://app.example.com');Teste também origem não permitida, preflight, credenciais e headers expostos.
Segurança operacional
Mantenha a lista de origens em configuração validada. Não aceite uma variável com curinga em produção sem alerta. Registre mudanças e remova domínios antigos depois de migrações.
Erros comuns
- usar curinga com cookies;
- considerar CORS autenticação;
- permitir qualquer origem refletindo o header recebido;
- validar subdomínio com string insegura;
- esquecer OPTIONS no proxy;
- não configurar Vary;
- duplicar headers em duas camadas;
- achar que CORS elimina CSRF;
- não testar redirects e erros.
Fluxo recomendado
- liste frontends autorizados;
- separe rotas públicas e privadas;
- permita métodos e headers mínimos;
- use origem explícita com credentials;
- proteja cookies contra CSRF;
- teste preflight;
- configure Vary e cache;
- monitore rejeições.
Combine CORS com Helmet no Node.js, Cache HTTP, Server-Sent Events e TLS e HTTPS.
Consulte a documentação do middleware cors e a referência de CORS da MDN.



