Erros de CORS e Pedidos Preflight OPTIONS Explicados Sem Frustração
O Pesadelo da Consola Vermelha: O Que Realmente Significa o Erro de CORS
Quase todos os programadores conhecem perfeitamente este momento de desespero: crias a tua aplicação frontend em localhost:3000 ou app.empresa.pt, fazes um fetch() para a tua API em api.empresa.pt, e a consola do teu navegador enche-se instantaneamente de texto vermelho:
Access to fetch at 'https://api.empresa.pt/v1/utilizadores' from origin 'https://app.empresa.pt'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
Sem compreenderem o mecanismo, muitos profissionais pesquisam no Google e acabam por colar a pior solução possível no código:
// O remendo perigoso que nunca deves usar:
res.setHeader('Access-Control-Allow-Origin', '*');
Este remendo, além de inseguro, parte de um erro de conceito básico: o CORS não é um erro gerado pelo teu servidor. Na verdade, o teu servidor backend processou o pedido normalmente e devolveu um código 200 OK perfeito!
Quem bloqueou a operação foi o próprio motor do teu navegador, que inspecionou a resposta, reparou que o servidor não incluiu uma autorização explícita para aquele domínio de origem e recusou entregar os dados ao teu código em JavaScript.
O BROWSER A INTERCETAR A RESPOSTA POR FALTA DE CORS
- O Teu JavaScript (Origem A)
- O Teu Servidor API (Origem B)
- fetch(‘https: //api.b.com/dados’) (Backend processa o pedido) (Backend devolve 200 OK)
-
- Resposta 200 OK (Sem cabeçalhos CORS!)
- A SEGURANÇA DO BROWSER INTERPRETA A RESPOSTA:
- O servidor autorizou a Origem A? NÃO.
- BLOQUEIO ATIVO: O browser esconde os dados!
- O teu JavaScript NUNCA RECEBE o JSON do pedido!
Tal como explicado pela OWASP, o CORS existe para proteger os teus utilizadores de sites maliciosos que tentem aproveitar as sessões abertas no computador deles. No desenho de web apps, dominar este comportamento é essencial.
Pedidos Simples vs Pedidos Preflight: O Que Faz Disparar o OPTIONS?
O navegador divide todos os pedidos entre domínios diferentes em duas categorias:
1. Pedidos Simples (Sem Preflight)
O pedido sai de imediato para a rede sem perguntas prévias se cumprir estas três condições:
- Método HTTP: Apenas
GET,HEADouPOST. - Cabeçalhos: Apenas os cabeçalhos normais do browser (
Accept,Accept-Language,Content-Language). - Tipo de Conteúdo: Exclusivamente
text/plain,multipart/form-dataouapplication/x-www-form-urlencoded.
Como os formulários HTML tradicionais já podiam fazer isto nos anos 90, o navegador deixa a comunicação passar diretamente.
2. Pedidos com Preflight (A Sonda OPTIONS)
No entanto, quando a tua aplicação moderna envia dados em formato estruturado—como um corpo em JSON (Content-Type: application/json) ou anexa um token de segurança (Authorization: Bearer <jwt>)—o navegador considera a ação potencialmente sensível.
Antes de deixar sair o pedido real, o browser envia uma pergunta preliminar em segundo plano usando o método HTTP OPTIONS. Pergunta ao servidor:
- “Pretendo enviar um POST com cabeçalho Authorization vindo de https://app.empresa.pt. Autorizas?”
O MECANISMO COMPLETO DE PREFLIGHT OPTIONS
- Browser do Utilizador
- Servidor Backend
-
- Pedido OPTIONS /v1/checkout
- Origin: https://app.empresa.pt
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: Authorization
-
- Resposta HTTP 204 No Content
- Access-Control-Allow-Origin: https://app.empresa.pt
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: Authorization
- Access-Control-Max-Age: 86400
- O BROWSER APROVA: Luz verde para o pedido real!
-
- Pedido Real POST /v1/checkout com dados em JSON
-
- Resposta HTTP 200 OK com confirmação de pagamento
Se o teu servidor não responder ao método OPTIONS ou se se esquecer de listar os cabeçalhos permitidos, o browser cancela a operação antes sequer de enviar o POST verdadeiro!
O Impacto no Desempenho: Eliminar Latência com Access-Control-Max-Age
Repara no custo invisível: cada ação do utilizador na tua aplicação passa a exigir duas viagens de rede completas (2 RTT). Em ligações de telemóvel com latência de 80ms, esperar pela resposta do OPTIONS e depois pelo POST real adiciona quase um terço de segundo de atraso a cada clique.
Para acelerares os teus websites de alta performance, deves instruir o browser a guardar esta permissão em cache através do cabeçalho Access-Control-Max-Age:
Access-Control-Max-Age: 86400
Com este valor (86.400 segundos = 24 horas), o navegador só faz o teste de preflight uma única vez por dia. Em todos os cliques seguintes, envia o pedido de forma imediata em apenas 1 RTT.
Configuração Segura de CORS no Servidor Nginx
Gerir regras de CORS diretamente no código da aplicação é propenso a falhas, especialmente se o teu backend devolver um erro 500 e perder os cabeçalhos no caminho. O método mais robusto e performante é tratar o CORS diretamente no proxy reverso com o Nginx.
Aqui tens uma configuração profissional com validação rigorosa de domínios:
# /etc/nginx/conf.d/cors_seguro.conf
map $http_origin $cors_autorizado {
default "";
"https://app.x078.com" $http_origin;
"https://staging.x078.com" $http_origin;
"http://localhost:3000" $http_origin; # Apenas para desenvolvimento local
}
server {
listen 443 ssl http2;
server_name api.x078.com;
location / {
# Trata o pedido preliminar de preflight OPTIONS
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_autorizado always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, Idempotency-Key' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' '86400' always;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' '0';
return 204;
}
# Respostas normais da aplicação
add_header 'Access-Control-Allow-Origin' $cors_autorizado always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Repara no cabeçalho Vary: Origin. Ele garante que caches intermediárias ou CDNs nunca entregam a resposta autorizada de um domínio a outro cliente com uma origem não autorizada. Podes encontrar mais exemplos de implementação no repositório de suporte no GitHub.
Boas Práticas Operacionais: Credenciais, Expressões Regulares e Cabeçalhos Expostos
Seguindo o princípio de anti-over-engineering e as diretrizes de produção da WebXtek, tem em conta estes três pontos vitais:
- Nunca Uses Asterisco com Credenciais: Se a tua API usar
Access-Control-Allow-Credentials: truepara ler cookies HttpOnly, o browser rejeita a ligação de imediato se oAccess-Control-Allow-Originfor um asterisco*. Tens de devolver o domínio exato validado. - Cuidado com Expressões Regulares Falaciosas: Uma validação superficial como
origin.includes("empresa.pt")pode ser explorada por um atacante que registeempresa.pt.atacante.com. Valida sempre o nome exato ou exige o ponto divisor na expressão regular. - Expõe Cabeçalhos Personalizados: Se a tua API devolver cabeçalhos próprios como
X-Total-Countpara paginação, o teu JavaScript não consegue aceder a eles a não ser que os declares emAccess-Control-Expose-Headers: X-Total-Count.
Ao compreenderes como funciona o preflight OPTIONS e ao configurares o teu Nginx com mapas limpos de origens e cache de 24 horas, eliminas de vez os erros vermelhos na consola sem comprometeres a segurança das tuas aplicações.
Na prática, aplicas exatamente estes padrões operacionais em setores exigentes como saas empresarial b2b e especialistas em devops e arquitetura cloud. Quando alinhas a tua estrutura através de web apps e websites de alta performance, transformas boas práticas técnicas numa vantagem competitiva real e mensurável para o teu negócio.
[+] [ INDEX_DIRECTORY ] 15 tópicos ▼
- O Pesadelo da Consola Vermelha: O Que Realmente Significa o Erro de CORS
- Pedidos Simples vs Pedidos Preflight: O Que Faz Disparar o OPTIONS?
- 1. Pedidos Simples (Sem Preflight)
- 2. Pedidos com Preflight (A Sonda OPTIONS)
- O Impacto no Desempenho: Eliminar Latência com Access-Control-Max-Age
- Configuração Segura de CORS no Servidor Nginx
- Boas Práticas Operacionais: Credenciais, Expressões Regulares e Cabeçalhos Expostos
- Autor
- Perguntas Frequentes
- O que é o CORS e porque é que o browser o bloqueia por omissão?
- O que dispara um pedido preflight OPTIONS no navegador?
- Porque é perigoso colocar Access-Control-Allow-Origin: * em APIs autenticadas?
- Como podes reduzir o atraso de rede causado pelos pedidos OPTIONS?
- Nós Relacionados
- Sequência do Caminho
Perguntas Frequentes
O que é o CORS e porque é que o browser o bloqueia por omissão?
O CORS (Cross-Origin Resource Sharing) é uma proteção de segurança do navegador concebida para impedir que um site malicioso envie pedidos para outra aplicação usando a tua sessão ativa. Faz cumprir a Política da Mesma Origem (SOP), permitindo que os servidores definam quem pode aceder aos seus dados.
Porque é perigoso colocar Access-Control-Allow-Origin: * em APIs autenticadas?
O asterisco (*) permite que qualquer site malicioso na internet chame a tua API a partir do browser do utilizador. Além disso, os browsers modernos rejeitam o pedido se tentares enviar cookies com Access-Control-Allow-Credentials: true em conjunto com o asterisco.
Como podes reduzir o atraso de rede causado pelos pedidos OPTIONS?
Ao devolveres o cabeçalho Access-Control-Max-Age na resposta do OPTIONS (por exemplo com 86400 segundos). Isso faz com que o browser guarde a autorização em cache durante 24 horas, eliminando o tempo de espera nos pedidos seguintes.
[ RELATED_NODES ]
> INICIAR_PROJETO
Precisa de um website que transmita confiança, apareça na pesquisa e dê mais força à sua presença digital? Comece a conversa aqui.