Skip to content

Instantly share code, notes, and snippets.

@lucis
Created November 5, 2025 12:48
Show Gist options
  • Save lucis/7314e23b1b19961e23c708b133e7292a to your computer and use it in GitHub Desktop.
Save lucis/7314e23b1b19961e23c708b133e7292a to your computer and use it in GitHub Desktop.
Simples guia sobre CORS (Gerado com Claude)

Guia Completo sobre CORS (Cross-Origin Resource Sharing)

O que é CORS?

CORS (Cross-Origin Resource Sharing) é um mecanismo de segurança implementado pelos navegadores que controla como recursos de uma origem (domínio, protocolo ou porta) podem ser acessados por páginas web de outra origem.

O Problema que CORS Resolve

Por padrão, navegadores implementam a Same-Origin Policy (Política de Mesma Origem), uma medida de segurança que impede que scripts de uma página acessem recursos de outra origem. Isso protege usuários contra ataques maliciosos, como:

  • Roubo de tokens de autenticação
  • Acesso não autorizado a APIs privadas
  • Ataques CSRF (Cross-Site Request Forgery)

Exemplo de origens diferentes:

https://meuapp.com          → Origem A
https://api.meuapp.com      → Origem B (subdomínio diferente)
https://meuapp.com:3000     → Origem C (porta diferente)
http://meuapp.com           → Origem D (protocolo diferente)

CORS permite que servidores relaxem essa restrição de forma controlada, especificando quais origens têm permissão para acessar seus recursos.

Como CORS Funciona

1. Requisições Simples

Para requisições consideradas "simples" (GET, HEAD, POST com certos tipos de conteúdo), o navegador:

  1. Envia a requisição com o header Origin
  2. O servidor responde com Access-Control-Allow-Origin
  3. O navegador verifica se a origem está permitida
// Cliente fazendo requisição
fetch('https://api.exemplo.com/dados', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  }
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Erro CORS:', error));

2. Requisições Preflight

Para requisições mais complexas (métodos PUT, DELETE, headers customizados), o navegador faz uma requisição OPTIONS antes da requisição real:

// Cliente fazendo requisição com header customizado
fetch('https://api.exemplo.com/dados', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'valor'
  },
  body: JSON.stringify({ nome: 'João' })
});

// O navegador automaticamente envia:
// 1. OPTIONS /dados (preflight)
// 2. POST /dados (requisição real, se preflight passar)

Onde CORS Funciona e Onde Não Funciona

✅ Onde CORS É Aplicado

  • Requisições XMLHttpRequest e Fetch API: APIs JavaScript para fazer requisições HTTP
  • Web Fonts (@font-face): Carregamento de fontes de outros domínios
  • WebGL Textures: Texturas carregadas de origens diferentes
  • Canvas drawImage(): Imagens de outros domínios desenhadas em canvas
  • CSS Shapes de imagens: Formas CSS baseadas em imagens externas

❌ Onde CORS NÃO É Aplicado

  • Tags HTML simples: <img>, <video>, <script>, <link> não são bloqueadas por CORS
  • Requisições de servidor para servidor: CORS é uma restrição apenas de navegadores
  • WebSockets: Usa seu próprio mecanismo de segurança
  • Formulários HTML tradicionais: Submissões via <form> não são bloqueadas
// ❌ CORS aplica aqui
fetch('https://outra-origem.com/api/dados');

// ✅ CORS NÃO aplica aqui (mas pode ter outras restrições)
const img = document.createElement('img');
img.src = 'https://outra-origem.com/imagem.jpg';

Headers CORS Principais

Headers de Resposta (Servidor)

Access-Control-Allow-Origin

Especifica quais origens podem acessar o recurso.

// Permitir origem específica
response.setHeader('Access-Control-Allow-Origin', 'https://meuapp.com');

// Permitir qualquer origem (⚠️ cuidado em produção!)
response.setHeader('Access-Control-Allow-Origin', '*');

// Permitir origem dinâmica
const origin = request.headers.origin;
if (origensPermitidas.includes(origin)) {
  response.setHeader('Access-Control-Allow-Origin', origin);
}

Access-Control-Allow-Methods

Lista os métodos HTTP permitidos.

response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');

Access-Control-Allow-Headers

Lista os headers que o cliente pode enviar.

response.setHeader(
  'Access-Control-Allow-Headers', 
  'Content-Type, Authorization, X-Requested-With'
);

Access-Control-Allow-Credentials

Permite envio de cookies e credenciais.

response.setHeader('Access-Control-Allow-Credentials', 'true');
// ⚠️ Quando true, Access-Control-Allow-Origin NÃO pode ser '*'
response.setHeader('Access-Control-Allow-Origin', 'https://meuapp.com');

Access-Control-Max-Age

Define por quanto tempo o resultado do preflight pode ser cacheado.

response.setHeader('Access-Control-Max-Age', '86400'); // 24 horas

Access-Control-Expose-Headers

Define quais headers a resposta pode expor para o JavaScript.

response.setHeader('Access-Control-Expose-Headers', 'X-Total-Count, X-Page-Number');

Headers de Requisição (Cliente)

Estes são adicionados automaticamente pelo navegador:

  • Origin: A origem da requisição
  • Access-Control-Request-Method: Método da requisição real (em preflight)
  • Access-Control-Request-Headers: Headers que serão enviados (em preflight)

Padrões de Solução

Solução 1: Configurar CORS no Backend

Esta é a solução recomendada e mais segura.

Node.js + Express

import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';

const app = express();

// Opção 1: Configuração simples (permite tudo)
app.use(cors());

// Opção 2: Configuração customizada
const corsOptions = {
  origin: ['https://meuapp.com', 'https://www.meuapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
};

app.use(cors(corsOptions));

// Opção 3: CORS condicional
app.use((req: Request, res: Response, next: NextFunction) => {
  const origin = req.headers.origin;
  const allowedOrigins = [
    'https://meuapp.com',
    'http://localhost:3000'
  ];

  if (origin && allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }

  if (req.method === 'OPTIONS') {
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.setHeader('Access-Control-Max-Age', '86400');
    return res.sendStatus(204);
  }

  next();
});

// Suas rotas
app.get('/api/dados', (req: Request, res: Response) => {
  res.json({ mensagem: 'Dados retornados com sucesso' });
});

Deno

import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";

const app = new Application();
const router = new Router();

// Habilitar CORS
app.use(oakCors({
  origin: "https://meuapp.com",
  credentials: true
}));

router.get("/api/dados", (ctx) => {
  ctx.response.body = { mensagem: "Dados retornados" };
});

app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: 8000 });

Next.js API Routes

// pages/api/dados.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Configurar CORS
  res.setHeader('Access-Control-Allow-Origin', 'https://meuapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  // Handle preflight
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  // Lógica da API
  if (req.method === 'GET') {
    return res.status(200).json({ mensagem: 'Sucesso' });
  }

  return res.status(405).json({ erro: 'Método não permitido' });
}

Fastify

import Fastify from 'fastify';
import cors from '@fastify/cors';

const fastify = Fastify({ logger: true });

// Registrar CORS
await fastify.register(cors, {
  origin: ['https://meuapp.com', 'http://localhost:3000'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE']
});

fastify.get('/api/dados', async (request, reply) => {
  return { mensagem: 'Dados retornados' };
});

await fastify.listen({ port: 3000 });

Solução 2: Proxy Reverso no Frontend

Quando você não tem controle sobre a API de terceiros, pode criar um proxy.

Next.js Rewrites (next.config.js)

// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/api/proxy/:path*',
        destination: 'https://api-externa.com/:path*'
      }
    ];
  }
};

// No componente
async function buscarDados() {
  // Chama o proxy local ao invés da API externa
  const response = await fetch('/api/proxy/dados');
  const data = await response.json();
  return data;
}

Vite Proxy (vite.config.ts)

import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'https://api-externa.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

// No código
fetch('/api/dados'); // Vite redireciona para https://api-externa.com/dados

Create React App Proxy (package.json)

{
  "proxy": "https://api-externa.com"
}
// No componente
fetch('/api/dados'); // CRA redireciona para https://api-externa.com/api/dados

Solução 3: Proxy Servidor Standalone

Para produção, você pode criar um servidor proxy dedicado.

// proxy-server.ts
import express, { Request, Response } from 'express';
import cors from 'cors';
import { createProxyMiddleware } from 'http-proxy-middleware';

const app = express();

// Habilitar CORS no proxy
app.use(cors({
  origin: 'https://meuapp.com',
  credentials: true
}));

// Configurar proxy
app.use('/api', createProxyMiddleware({
  target: 'https://api-externa.com',
  changeOrigin: true,
  pathRewrite: {
    '^/api': '' // Remove /api do caminho
  },
  onProxyReq: (proxyReq, req, res) => {
    // Adicionar headers customizados
    proxyReq.setHeader('X-Proxy-By', 'MeuApp');
  },
  onProxyRes: (proxyRes, req, res) => {
    // Modificar resposta se necessário
    console.log('Resposta da API externa:', proxyRes.statusCode);
  }
}));

app.listen(3001, () => {
  console.log('Proxy rodando na porta 3001');
});

Solução 4: Serverless Functions

Vercel Serverless Function

// api/proxy.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';

export default async function handler(
  req: VercelRequest,
  res: VercelResponse
) {
  // Configurar CORS
  res.setHeader('Access-Control-Allow-Origin', 'https://meuapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }

  try {
    // Fazer requisição para API externa
    const response = await fetch('https://api-externa.com/dados', {
      method: req.method,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_KEY}`
      }
    });

    const data = await response.json();
    return res.status(200).json(data);
  } catch (error) {
    return res.status(500).json({ erro: 'Erro ao buscar dados' });
  }
}

AWS Lambda + API Gateway

// lambda-proxy.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  const headers = {
    'Access-Control-Allow-Origin': 'https://meuapp.com',
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Allow-Credentials': 'true'
  };

  // Handle preflight
  if (event.httpMethod === 'OPTIONS') {
    return {
      statusCode: 200,
      headers,
      body: ''
    };
  }

  try {
    const response = await fetch('https://api-externa.com/dados');
    const data = await response.json();

    return {
      statusCode: 200,
      headers,
      body: JSON.stringify(data)
    };
  } catch (error) {
    return {
      statusCode: 500,
      headers,
      body: JSON.stringify({ erro: 'Erro ao buscar dados' })
    };
  }
};

Solução 5: JSONP (Legado)

⚠️ Não recomendado: Apenas para APIs antigas que não suportam CORS.

function jsonp(url: string, callbackName: string): Promise<any> {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    
    // Criar callback global
    (window as any)[callbackName] = (data: any) => {
      delete (window as any)[callbackName];
      document.body.removeChild(script);
      resolve(data);
    };

    script.onerror = () => {
      delete (window as any)[callbackName];
      document.body.removeChild(script);
      reject(new Error('Erro ao carregar JSONP'));
    };

    script.src = `${url}?callback=${callbackName}`;
    document.body.appendChild(script);
  });
}

// Uso
jsonp('https://api.exemplo.com/dados', 'minhaCallback')
  .then(data => console.log(data))
  .catch(error => console.error(error));

Problemas Comuns e Soluções

Problema 1: Credenciais com Wildcard

// ❌ ERRO: Não funciona
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Allow-Credentials', 'true');

// ✅ CORRETO
const origin = request.headers.origin;
response.setHeader('Access-Control-Allow-Origin', origin);
response.setHeader('Access-Control-Allow-Credentials', 'true');

Problema 2: Headers Customizados Não Expostos

// Backend
app.get('/api/dados', (req: Request, res: Response) => {
  res.setHeader('X-Total-Count', '100');
  res.setHeader('X-Page-Number', '1');
  
  // ✅ NECESSÁRIO para o cliente acessar esses headers
  res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count, X-Page-Number');
  
  res.json({ dados: [] });
});

// Frontend
fetch('/api/dados')
  .then(response => {
    const total = response.headers.get('X-Total-Count');
    console.log('Total:', total); // Agora funciona
  });

Problema 3: Preflight Cache

// Se suas configurações mudam frequentemente:
response.setHeader('Access-Control-Max-Age', '600'); // 10 minutos

// Para desenvolvimento (sem cache):
response.setHeader('Access-Control-Max-Age', '0');

// Para produção (cache longo):
response.setHeader('Access-Control-Max-Age', '86400'); // 24 horas

Problema 4: CORS em Cookies

// Backend
app.use(cors({
  origin: 'https://meuapp.com', // Origem específica
  credentials: true // Permite cookies
}));

// Frontend
fetch('https://api.exemplo.com/login', {
  method: 'POST',
  credentials: 'include', // ✅ IMPORTANTE: Inclui cookies
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ email, senha })
});

Boas Práticas

1. Seja Específico com Origens

// ❌ Evite em produção
app.use(cors({ origin: '*' }));

// ✅ Prefira lista específica
const origensPermitidas = [
  'https://meuapp.com',
  'https://www.meuapp.com',
  'https://admin.meuapp.com'
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || origensPermitidas.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Origem não permitida por CORS'));
    }
  }
}));

2. Use Variáveis de Ambiente

// .env
ALLOWED_ORIGINS=https://meuapp.com,https://www.meuapp.com
CORS_MAX_AGE=86400

// server.ts
import dotenv from 'dotenv';
dotenv.config();

const origensPermitidas = process.env.ALLOWED_ORIGINS?.split(',') || [];

app.use(cors({
  origin: origensPermitidas,
  maxAge: parseInt(process.env.CORS_MAX_AGE || '3600')
}));

3. Crie um Middleware Reutilizável

// middleware/cors.ts
import { Request, Response, NextFunction } from 'express';

interface CorsOptions {
  origins: string[];
  methods?: string[];
  credentials?: boolean;
}

export function corsMiddleware(options: CorsOptions) {
  return (req: Request, res: Response, next: NextFunction) => {
    const origin = req.headers.origin;

    if (origin && options.origins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      
      if (options.credentials) {
        res.setHeader('Access-Control-Allow-Credentials', 'true');
      }
    }

    if (req.method === 'OPTIONS') {
      res.setHeader(
        'Access-Control-Allow-Methods',
        options.methods?.join(', ') || 'GET, POST, PUT, DELETE'
      );
      res.setHeader(
        'Access-Control-Allow-Headers',
        'Content-Type, Authorization'
      );
      res.setHeader('Access-Control-Max-Age', '86400');
      return res.sendStatus(204);
    }

    next();
  };
}

// Uso
app.use(corsMiddleware({
  origins: ['https://meuapp.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE']
}));

4. Log de Requisições CORS Bloqueadas

app.use((req: Request, res: Response, next: NextFunction) => {
  const origin = req.headers.origin;
  
  if (origin && !origensPermitidas.includes(origin)) {
    console.warn(`🚫 CORS bloqueado para origem: ${origin}`);
    console.warn(`   Rota: ${req.method} ${req.path}`);
    console.warn(`   IP: ${req.ip}`);
  }
  
  next();
});

Debugging CORS

Checklist de Debug

// 1. Verificar headers da resposta
fetch('https://api.exemplo.com/dados')
  .then(response => {
    console.log('CORS Headers:');
    console.log('Allow-Origin:', response.headers.get('Access-Control-Allow-Origin'));
    console.log('Allow-Methods:', response.headers.get('Access-Control-Allow-Methods'));
    console.log('Allow-Headers:', response.headers.get('Access-Control-Allow-Headers'));
  });

// 2. Verificar se é erro de CORS ou outro erro
fetch('https://api.exemplo.com/dados')
  .then(response => {
    console.log('Status:', response.status);
    return response.json();
  })
  .catch(error => {
    if (error.message.includes('CORS')) {
      console.error('❌ Erro de CORS');
    } else if (error.message.includes('Failed to fetch')) {
      console.error('❌ Erro de rede ou CORS');
    } else {
      console.error('❌ Outro erro:', error);
    }
  });

Tool Helper para Debug

// debug-cors.ts
export class CorsDebugger {
  static async testEndpoint(url: string): Promise<void> {
    console.log(`\n🔍 Testando CORS para: ${url}\n`);

    try {
      // Teste 1: OPTIONS (Preflight)
      console.log('1️⃣ Testando requisição OPTIONS...');
      const optionsResponse = await fetch(url, { method: 'OPTIONS' });
      
      console.log('   Status:', optionsResponse.status);
      console.log('   Allow-Origin:', optionsResponse.headers.get('Access-Control-Allow-Origin'));
      console.log('   Allow-Methods:', optionsResponse.headers.get('Access-Control-Allow-Methods'));
      console.log('   Allow-Headers:', optionsResponse.headers.get('Access-Control-Allow-Headers'));
      console.log('   Max-Age:', optionsResponse.headers.get('Access-Control-Max-Age'));

      // Teste 2: GET
      console.log('\n2️⃣ Testando requisição GET...');
      const getResponse = await fetch(url);
      
      console.log('   Status:', getResponse.status);
      console.log('   Allow-Origin:', getResponse.headers.get('Access-Control-Allow-Origin'));
      
      if (getResponse.ok) {
        console.log('   ✅ CORS configurado corretamente!');
      }

    } catch (error) {
      console.error('   ❌ Erro:', error);
      console.log('\n💡 Possíveis causas:');
      console.log('   - Backend não configurou headers CORS');
      console.log('   - Origem não está na lista permitida');
      console.log('   - Servidor não está respondendo');
    }
  }

  static checkBrowserSupport(): void {
    const supportsCredentials = 'withCredentials' in new XMLHttpRequest();
    const supportsFetch = typeof fetch !== 'undefined';
    
    console.log('Suporte do navegador:');
    console.log('  Credentials:', supportsCredentials ? '✅' : '❌');
    console.log('  Fetch API:', supportsFetch ? '✅' : '❌');
  }
}

// Uso
CorsDebugger.testEndpoint('https://api.exemplo.com/dados');
CorsDebugger.checkBrowserSupport();

Segurança

Não Confie Cegamente em CORS

CORS é uma proteção do navegador, não do servidor. Ferramentas como cURL, Postman e scripts não são afetadas por CORS.

// ✅ SEMPRE valide no backend
app.post('/api/dados-sensiveis', (req: Request, res: Response) => {
  // 1. Verificar autenticação
  const token = req.headers.authorization;
  if (!validarToken(token)) {
    return res.status(401).json({ erro: 'Não autorizado' });
  }

  // 2. Verificar permissões
  if (!usuarioTemPermissao(req.user, 'ler-dados')) {
    return res.status(403).json({ erro: 'Sem permissão' });
  }

  // 3. Validar dados
  if (!validarDados(req.body)) {
    return res.status(400).json({ erro: 'Dados inválidos' });
  }

  // CORS sozinho NÃO protege!
  // Processar requisição...
});

CORS + CSRF Protection

import csrf from 'csurf';
import cookieParser from 'cookie-parser';

app.use(cookieParser());
app.use(csrf({ cookie: true }));

app.get('/api/csrf-token', (req: Request, res: Response) => {
  res.json({ csrfToken: req.csrfToken() });
});

app.post('/api/acao-importante', (req: Request, res: Response) => {
  // Token CSRF é validado automaticamente
  res.json({ sucesso: true });
});

// Frontend
const response = await fetch('/api/csrf-token');
const { csrfToken } = await response.json();

await fetch('/api/acao-importante', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'CSRF-Token': csrfToken
  },
  credentials: 'include',
  body: JSON.stringify({ dados })
});

Resumo das Estratégias

Estratégia Quando Usar Prós Contras
Headers CORS Você controla o backend Solução nativa e segura Requer acesso ao backend
Proxy Dev Desenvolvimento local Rápido de configurar Só funciona em dev
Proxy Produção API de terceiros sem CORS Funciona em produção Adiciona latência
Serverless Microsserviços Escalável, sem infra Cold start
JSONP APIs legadas Compatível com tudo Inseguro, limitado

Conclusão

CORS é uma medida de segurança essencial na web moderna. A melhor abordagem é sempre configurar CORS corretamente no backend. Use proxies apenas quando não tiver acesso ao servidor da API.

Pontos-chave:

  • CORS protege o usuário, não o servidor
  • Sempre valide autenticação e permissões no backend
  • Seja específico com origens permitidas
  • Use variáveis de ambiente para configuração
  • Teste diferentes cenários (preflight, credenciais, headers customizados)
  • Lembre-se: CORS não substitui outras camadas de segurança

Recursos Adicionais


Autor: Lucis
Data: Novembro 2025
Versão: 1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment