CORS: erros comuns e configurações de servidor

1. Entendendo o CORS: Origem e Mecanismo

CORS (Cross-Origin Resource Sharing) é um mecanismo de segurança implementado pelos navegadores que permite ou restringe requisições HTTP entre diferentes origens. Uma origem é definida pela combinação de protocolo, domínio e porta. Por exemplo, https://api.exemplo.com e https://app.exemplo.com são origens diferentes.

O CORS existe para proteger os usuários contra ataques como CSRF (Cross-Site Request Forgery) e vazamento de dados. Sem ele, qualquer site malicioso poderia fazer requisições arbitrárias para outros domínios e ler as respostas.

Existem dois tipos de requisições CORS:
- Requisições simples: GET, HEAD ou POST com Content-Type limitado (text/plain, application/x-www-form-urlencoded, multipart/form-data)
- Requisições preflight: qualquer outra requisição que dispara uma requisição OPTIONS antes da requisição real

Headers fundamentais no CORS:

# Headers de requisição (enviados pelo navegador)
Origin: https://meuapp.com

# Headers de resposta (enviados pelo servidor)
Access-Control-Allow-Origin: https://meuapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

2. Erro Clássico: "No 'Access-Control-Allow-Origin' header is present"

Este é o erro mais comum ao implementar CORS. O navegador bloqueia a requisição porque o servidor não inclui o header Access-Control-Allow-Origin na resposta.

Causa: O servidor não está configurado para aceitar requisições de origens diferentes.

Como depurar:
1. Abra o console do navegador (F12)
2. Vá para a aba "Network" (Rede)
3. Faça a requisição e observe a resposta
4. Verifique se os headers CORS estão presentes

Solução:

# Configuração no servidor (exemplo em Express.js)
const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
  next();
});

# Ou usando middleware específico
const cors = require('cors');
app.use(cors({ origin: '*' }));

3. Erro com Credenciais: Cookies e Headers de Autenticação

Quando você precisa enviar cookies ou headers de autenticação (como Authorization), o cenário se torna mais restritivo.

Erro comum: "The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when credentials mode is 'include'"

Problema: Ao usar withCredentials: true no frontend, o servidor não pode usar * como origem.

Configuração correta:

# Frontend (JavaScript)
fetch('https://api.exemplo.com/dados', {
  method: 'GET',
  credentials: 'include',  // ou 'same-origin'
  headers: {
    'Authorization': 'Bearer token123'
  }
});

# Backend (Express.js)
app.use(cors({
  origin: 'https://meuapp.com',  // Origem específica, não usar '*'
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE']
}));

4. Falhas em Requisições Preflight (OPTIONS)

Requisições preflight são disparadas automaticamente pelo navegador para métodos que podem modificar dados (PUT, DELETE, PATCH) ou quando headers personalizados são usados.

Erro: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource"

Solução: Configurar o servidor para responder adequadamente às requisições OPTIONS.

# Configuração para preflight
app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://meuapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Max-Age', '86400');  // Cache por 24 horas
  res.status(204).end();
});

5. Headers Personalizados e Métodos Não-Padrão

Headers personalizados (como X-Requested-With, X-Custom-Header) e métodos como PUT, DELETE e PATCH sempre disparam requisições preflight.

Erro: "Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response"

Solução: Listar explicitamente todos os headers e métodos permitidos.

# Configuração que permite headers personalizados
app.use(cors({
  origin: 'https://meuapp.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header', 'X-Requested-With']
}));

6. Configurações de Servidor: Exemplos Práticos

Nginx

server {
    listen 80;
    server_name api.exemplo.com;

    location / {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'https://app.exemplo.com';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Max-Age' 86400;
            return 204;
        }

        add_header 'Access-Control-Allow-Origin' 'https://app.exemplo.com';
        add_header 'Access-Control-Allow-Credentials' 'true';
        proxy_pass http://localhost:3000;
    }
}

Apache

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://app.exemplo.com"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

Django (Python)

# settings.py
INSTALLED_APPS = [
    'corsheaders',
    # ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    # ...
]

CORS_ALLOWED_ORIGINS = [
    "https://app.exemplo.com",
]

CORS_ALLOW_CREDENTIALS = True

Spring Boot (Java)

// WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://app.exemplo.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

7. Estratégias Avançadas e Debugging

Lidando com múltiplas origens dinamicamente

# Permite múltiplas origens baseadas em whitelist
const allowedOrigins = ['https://app1.com', 'https://app2.com'];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  next();
});

Proxy reverso para desenvolvimento

# Exemplo com webpack-dev-server
// webpack.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true
      }
    }
  }
};

Ferramentas de debugging

  • Extensões de navegador: CORS Toggle, Allow CORS
  • Ferramentas de rede: Chrome DevTools, Firefox Developer Tools
  • Simulação de requisições: Postman, curl com headers personalizados
# Testando CORS com curl
curl -H "Origin: https://meuapp.com" \
  -H "Access-Control-Request-Method: GET" \
  -X OPTIONS \
  -v https://api.exemplo.com/dados

Referências