Erros de CORS e Pedidos Preflight OPTIONS Explicados Sem Frustração

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)
  1. fetch(‘https: //api.b.com/dados’) (Backend processa o pedido) (Backend devolve 200 OK)
    1. 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, HEAD ou POST.
  • Cabeçalhos: Apenas os cabeçalhos normais do browser (Accept, Accept-Language, Content-Language).
  • Tipo de Conteúdo: Exclusivamente text/plain, multipart/form-data ou application/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:

O MECANISMO COMPLETO DE PREFLIGHT OPTIONS

  • Browser do Utilizador
  • Servidor Backend
    1. Pedido OPTIONS /v1/checkout
  • Origin: https://app.empresa.pt
  • Access-Control-Request-Method: POST
  • Access-Control-Request-Headers: Authorization
    1. 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!
    1. Pedido Real POST /v1/checkout com dados em JSON
    1. 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:

  1. Nunca Uses Asterisco com Credenciais: Se a tua API usar Access-Control-Allow-Credentials: true para ler cookies HttpOnly, o browser rejeita a ligação de imediato se o Access-Control-Allow-Origin for um asterisco *. Tens de devolver o domínio exato validado.
  2. Cuidado com Expressões Regulares Falaciosas: Uma validação superficial como origin.includes("empresa.pt") pode ser explorada por um atacante que registe empresa.pt.atacante.com. Valida sempre o nome exato ou exige o ponto divisor na expressão regular.
  3. Expõe Cabeçalhos Personalizados: Se a tua API devolver cabeçalhos próprios como X-Total-Count para paginação, o teu JavaScript não consegue aceder a eles a não ser que os declares em Access-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 ▼
[ SYSTEM.FAQ ]

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.

O que dispara um pedido preflight OPTIONS no navegador?

Um pedido preflight OPTIONS é gerado automaticamente sempre que o teu pedido não é 'Simples'. Isso acontece se usares métodos como PUT ou DELETE, tipos de conteúdo modernos como application/json, ou cabeçalhos personalizados como Authorization.

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.

> 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.