VULNEXUSAI · BLOG
Erro de CORS: como resolver no Node.js e Nginx sem usar wildcard
Erro de CORS bloqueando sua API em produção? Veja como configurar Access-Control-Allow-Origin corretamente no Express, Nginx e Next.js — sem usar * e sem quebrar o cache de CDN.
Se o console do navegador mostra Access to fetch at '...' from origin '...' has been blocked by CORS policy, o problema não está no frontend — está na configuração do servidor. O browser está fazendo exatamente o que deve: bloqueando uma resposta que o servidor não autorizou explicitamente.
Por que o erro aparece só em produção
Em desenvolvimento você acessa tudo via localhost, então o mesmo servidor responde o frontend e a API — sem CORS. Em produção, o frontend fica em app.meusite.com e a API em api.meusite.com (ou meusite.com/api). São origens diferentes. O browser exige o header Access-Control-Allow-Origin correto; sem ele, bloqueia.
Se quiser entender o mecanismo completo por trás desse bloqueio antes de mexer na configuração, veja o que é CORS e como ele funciona.
Node.js / Express com allowlist de origens
Nunca use Access-Control-Allow-Origin: * em rotas autenticadas — cookies e headers de Authorization não são enviados com wildcard. Use uma allowlist:
const ALLOWED_ORIGINS = [
'https://meuapp.com',
'https://www.meuapp.com',
'https://staging.meuapp.com',
];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin'); // evita cache incorreto na CDN
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization'
);
res.setHeader(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, OPTIONS'
);
}
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
O header Vary: Origin é obrigatório sempre que você reflete a origem dinamicamente: sem ele, uma CDN pode cachear a resposta com o Access-Control-Allow-Origin de um cliente e servir para outro, causando erros silenciosos.
Nginx com map (sem if dentro do location)
A documentação do Nginx desaconselha if dentro de blocos location. Use map no nível http:
map $http_origin $cors_origin {
default "";
"https://meuapp.com" $http_origin;
"https://www.meuapp.com" $http_origin;
"https://staging.meuapp.com" $http_origin;
}
server {
listen 443 ssl;
server_name api.meuapp.com;
location / {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 86400;
return 204;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
add_header Access-Control-Allow-Credentials true always;
proxy_pass http://localhost:3000;
}
}
Next.js (App Router) com Route Handler dinâmico
// app/api/dados/route.ts
const ALLOWED_ORIGINS = [
'https://meuapp.com',
'https://www.meuapp.com',
];
function corsHeaders(origin: string | null) {
const allowed = origin && ALLOWED_ORIGINS.includes(origin) ? origin : '';
return {
'Access-Control-Allow-Origin': allowed,
'Vary': 'Origin',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
}
export async function OPTIONS(req: Request) {
const origin = req.headers.get('origin');
return new Response(null, { status: 204, headers: corsHeaders(origin) });
}
export async function GET(req: Request) {
const origin = req.headers.get('origin');
const data = { ok: true };
return Response.json(data, { headers: corsHeaders(origin) });
}
Erros comuns que quebram a produção
Wildcard com credenciais — Access-Control-Allow-Origin: * combinado com Access-Control-Allow-Credentials: true é inválido pelo spec. O browser rejeita a resposta mesmo que o servidor envie.
Preflight sem resposta 200/204 — Requisições com Content-Type: application/json ou headers customizados disparam um preflight OPTIONS. Se o servidor retornar 404 ou 405 para OPTIONS, o browser aborta antes de enviar a requisição real.
Header duplicado — Se o proxy (Nginx) e a aplicação (Express) ambos adicionam Access-Control-Allow-Origin, o browser recebe dois valores e rejeita. Escolha um lugar para adicionar.
Vary: Origin ausente — Quando o servidor reflete a origem dinamicamente, o Vary: Origin sinaliza para CDNs e proxies que a resposta varia por origem. Sem ele, a CDN pode entregar uma resposta cacheada com a origem errada para um cliente diferente.
Porta diferente conta como origem diferente — http://localhost:3000 e http://localhost:5173 são origens distintas. Adicione ambas na allowlist durante o desenvolvimento.
Configurou o CORS mas ainda tem dúvidas sobre quais outros headers de segurança sua API está deixando de enviar? O scanner da VulnexusAI verifica CORS, HSTS, CSP e mais de 15 outros pontos em segundos.
Perguntas frequentes
Por que Access-Control-Allow-Origin: * falha com cookies ou headers de Authorization?
O spec de CORS proíbe combinar uma origem wildcard com Access-Control-Allow-Credentials: true. Os navegadores rejeitam a resposta diretamente. Você precisa devolver uma origem específica da allowlist em vez de um wildcard sempre que houver credenciais envolvidas.
Por que preciso de Vary: Origin se já defino Access-Control-Allow-Origin dinamicamente?
Sem Vary: Origin, uma CDN ou proxy pode cachear a resposta gerada para uma origem e entregar essa mesma resposta cacheada para outra origem, causando falhas de CORS intermitentes e difíceis de reproduzir.
Por que minha requisição POST falha com erro de CORS embora o GET funcione?
Requisições com corpo JSON ou headers personalizados disparam primeiro uma requisição preflight OPTIONS. Se o seu servidor não tratar OPTIONS explicitamente e devolver um 200 ou 204, o navegador aborta antes de enviar a requisição POST real.
Leia também
CORS explicado: o que é e como configurar com segurança
O CORS controla se um site pode acessar recursos de outro. Entenda como funciona, quais configurações são perigosas (reflexão de origem, wildcard com credenciais) e como configurar.
Ler artigoContent-Security-Policy explicada para desenvolvedores
A CSP define exatamente o que o navegador pode carregar no seu site. Veja as diretivas principais, como configurar sem quebrar nada e como monitorar violações.
Ler artigoTeste qualquer URL pública no scanner gratuito da VulnexusAI e veja um score de 0 a 100, com nota de A a F e dicas de correção.
Verificar meu site