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.
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.
Para requisições consideradas "simples" (GET, HEAD, POST com certos tipos de conteúdo), o navegador:
- Envia a requisição com o header
Origin - O servidor responde com
Access-Control-Allow-Origin - 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));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)- 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
- 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';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);
}Lista os métodos HTTP permitidos.
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');Lista os headers que o cliente pode enviar.
response.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, X-Requested-With'
);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');Define por quanto tempo o resultado do preflight pode ser cacheado.
response.setHeader('Access-Control-Max-Age', '86400'); // 24 horasDefine quais headers a resposta pode expor para o JavaScript.
response.setHeader('Access-Control-Expose-Headers', 'X-Total-Count, X-Page-Number');Estes são adicionados automaticamente pelo navegador:
Origin: A origem da requisiçãoAccess-Control-Request-Method: Método da requisição real (em preflight)Access-Control-Request-Headers: Headers que serão enviados (em preflight)
Esta é a solução recomendada e mais segura.
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' });
});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 });// 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' });
}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 });Quando você não tem controle sobre a API de terceiros, pode criar um proxy.
// 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;
}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{
"proxy": "https://api-externa.com"
}// No componente
fetch('/api/dados'); // CRA redireciona para https://api-externa.com/api/dadosPara 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');
});// 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' });
}
}// 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' })
};
}
};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));// ❌ 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');// 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
});// 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// 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 })
});// ❌ 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'));
}
}
}));// .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')
}));// 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']
}));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();
});// 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);
}
});// 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();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...
});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 })
});| 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 |
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.
- 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
Autor: Lucis
Data: Novembro 2025
Versão: 1.0