Use Cases (Atualizado: 02/06/2026)

Guia completo de CORS com Claude Code: APIs cross-origin seguras

Configure CORS com Claude Code: preflight, credentials, allowlist de origin, comandos de teste e prompts de revisão.

Guia completo de CORS com Claude Code: APIs cross-origin seguras

Configurar CORS corretamente com Claude Code

Um frontend em localhost:3000 e uma API em localhost:8787 já podem gerar erro de CORS no navegador. A correção rápida costuma ser adicionar Access-Control-Allow-Origin: *, mas isso fica perigoso quando a API usa cookies, header Authorization ou painel administrativo.

CORS, Cross-Origin Resource Sharing, é o mecanismo pelo qual o servidor informa quais outros origins podem ler suas respostas a partir de JavaScript no navegador. Um origin combina scheme, host e port. https://app.example.com, https://api.example.com, http://localhost:3000 e http://localhost:5173 são origins diferentes.

Este guia separa as decisões para que Claude Code ajude sem esconder os riscos. Você terá exemplos copiáveis para Express, Fastify, Cloudflare Workers e Next.js Route Handler, além de preflight, credentials, allowlist de origin, comandos de teste e prompts de revisão.

O ponto central: CORS não é autenticação. Ele só controla se JavaScript no navegador pode ler uma resposta cross-origin. Não bloqueia curl, chamadas servidor a servidor nem usuários sem permissão. Autenticação, autorização, CSRF, rate limiting e headers de segurança devem ser tratados separadamente.

sequenceDiagram
  participant Browser as Browser
  participant API as API server
  Browser->>API: OPTIONS /api/messages<br/>Origin + Access-Control-Request-*
  API-->>Browser: 204 + Access-Control-Allow-*
  Browser->>API: POST /api/messages<br/>Cookie or Authorization
  API-->>Browser: 200 + Access-Control-Allow-Origin

Decisões antes do código

Defina estes valores antes de pedir uma configuração CORS ao Claude Code. Requisitos vagos tendem a gerar exemplos permissivos demais para produção.

DecisãoExemploAtenção
Origins permitidoshttps://app.example.com, https://admin.example.comSem path e sem barra final
CredentialsCookie, Authorization headerCom cookies, revise também SameSite=None; Secure
MétodosGET,POST,PUT,PATCH,DELETE,OPTIONSPermita só o que a API usa
HeadersContent-Type,Authorization,X-Request-IDDevem bater com o preflight

Preflight é a checagem que o navegador faz antes da requisição real. Para POST JSON, Authorization, PUT, DELETE e muitos headers customizados, ele envia OPTIONS primeiro. Sem Access-Control-Allow-Methods e Access-Control-Allow-Headers compatíveis, a requisição real não é enviada.

Configuração no Express

O exemplo assume Node.js 20 ou superior. O middleware oficial cors do Express aceita uma função em origin, permitindo validar cada request contra uma allowlist. Como a API usa credentials, ela reflete apenas origins permitidos e define credentials: true.

npm init -y
npm install express cors
node server.mjs
// server.mjs
import express from "express";
import cors from "cors";

const app = express();

const allowedOrigins = new Set([
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
  "http://localhost:5173",
]);

function isAllowedOrigin(origin) {
  if (!origin) return true;
  if (allowedOrigins.has(origin)) return true;
  return process.env.NODE_ENV !== "production" && /^http:\/\/localhost:\d+$/.test(origin);
}

const corsOptions = {
  origin(origin, callback) {
    callback(null, isAllowedOrigin(origin));
  },
  credentials: true,
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
  exposedHeaders: ["X-Request-ID"],
  maxAge: 86400,
  optionsSuccessStatus: 204,
};

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (origin && !isAllowedOrigin(origin)) {
    return res.status(403).json({ error: "Origin not allowed" });
  }
  next();
});

app.use(cors(corsOptions));
app.use(express.json());

app.get("/api/health", (_req, res) => {
  res.setHeader("X-Request-ID", crypto.randomUUID());
  res.json({ ok: true });
});

app.post("/api/messages", (req, res) => {
  res.setHeader("X-Request-ID", crypto.randomUUID());
  res.json({ ok: true, received: req.body });
});

app.listen(8787, () => {
  console.log("API listening on http://localhost:8787");
});

Em produção, rode com NODE_ENV=production e mantenha apenas domínios reais em allowedOrigins. Requests sem header Origin não são CORS de navegador, por isso o exemplo permite a passagem; API keys, JWT e permissões ainda precisam ser validados no middleware de autenticação.

Configuração no Fastify

Fastify usa @fastify/cors. O README oficial aceita boolean, string, array, RegExp e function para origin, mas uma checagem exata com Set é mais simples de auditar. Evite regex amplas sem necessidade real.

npm init -y
npm install fastify @fastify/cors
node server.mjs
// server.mjs
import Fastify from "fastify";
import cors from "@fastify/cors";

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

const allowedOrigins = new Set([
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
  "http://localhost:5173",
]);

function isAllowedOrigin(origin) {
  if (!origin) return true;
  if (allowedOrigins.has(origin)) return true;
  return process.env.NODE_ENV !== "production" && /^http:\/\/localhost:\d+$/.test(origin);
}

app.addHook("onRequest", async (request, reply) => {
  const origin = request.headers.origin;
  if (origin && !isAllowedOrigin(origin)) {
    return reply.code(403).send({ error: "Origin not allowed" });
  }
});

await app.register(cors, {
  origin(origin, callback) {
    callback(null, isAllowedOrigin(origin));
  },
  credentials: true,
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
  exposedHeaders: ["X-Request-ID"],
  maxAge: 86400,
  strictPreflight: true,
});

app.get("/api/health", async () => ({ ok: true }));

app.post("/api/messages", async (request) => {
  return { ok: true, received: request.body };
});

await app.listen({ port: 8787, host: "0.0.0.0" });

No Fastify, a ordem de plugins e hooks importa. Se um hook de autenticação rejeitar OPTIONS antes do plugin CORS responder, o navegador nunca fará a requisição real. Peça ao Claude Code para revisar também a ordem de registro.

Configuração no Cloudflare Workers

Cloudflare Workers usa a API Fetch padrão. Trate OPTIONS explicitamente, adicione headers CORS em sucesso e erro, e use Vary: Origin quando a resposta variar por origin.

// src/index.ts
const allowedOrigins = new Set([
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]);

function getCorsHeaders(request: Request): HeadersInit | null {
  const origin = request.headers.get("Origin");
  if (!origin) return {};
  if (!allowedOrigins.has(origin)) return null;

  return {
    "Access-Control-Allow-Origin": origin,
    "Access-Control-Allow-Credentials": "true",
    "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type,Authorization,X-Request-ID",
    "Access-Control-Max-Age": "86400",
    "Vary": "Origin",
  };
}

export default {
  async fetch(request: Request): Promise<Response> {
    const corsHeaders = getCorsHeaders(request);
    if (corsHeaders === null) {
      return Response.json({ error: "Origin not allowed" }, { status: 403 });
    }

    if (request.method === "OPTIONS") {
      return new Response(null, { status: 204, headers: corsHeaders });
    }

    const url = new URL(request.url);
    if (url.pathname === "/api/messages" && request.method === "POST") {
      const body = await request.json().catch(() => ({}));
      return Response.json({ ok: true, received: body }, { headers: corsHeaders });
    }

    return Response.json({ error: "Not found" }, { status: 404, headers: corsHeaders });
  },
};

O erro comum no Workers é adicionar headers apenas no caminho feliz. Se OPTIONS, 401, 403 ou 500 não tiverem CORS, o DevTools pode mostrar só uma falha de CORS e esconder o erro real.

Configuração no Next.js Route Handler

Com App Router, app/api/.../route.ts usa Request e Response padrão da Web. A documentação do Next.js mostra como adicionar CORS a uma resposta; para APIs com credentials, use allowlist em vez de *.

// app/api/messages/route.ts
const allowedOrigins = new Set([
  "https://app.example.com",
  "https://admin.example.com",
  "http://localhost:3000",
]);

function getCorsHeaders(request: Request): HeadersInit | null {
  const origin = request.headers.get("Origin");
  if (!origin) return {};
  if (!allowedOrigins.has(origin)) return null;

  return {
    "Access-Control-Allow-Origin": origin,
    "Access-Control-Allow-Credentials": "true",
    "Access-Control-Allow-Methods": "POST,OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type,Authorization,X-Request-ID",
    "Access-Control-Max-Age": "86400",
    "Vary": "Origin",
  };
}

export async function OPTIONS(request: Request) {
  const headers = getCorsHeaders(request);
  if (headers === null) {
    return Response.json({ error: "Origin not allowed" }, { status: 403 });
  }
  return new Response(null, { status: 204, headers });
}

export async function POST(request: Request) {
  const headers = getCorsHeaders(request);
  if (headers === null) {
    return Response.json({ error: "Origin not allowed" }, { status: 403 });
  }

  const body = await request.json().catch(() => ({}));
  return Response.json({ ok: true, received: body }, { headers });
}

headers() no next.config.js serve para headers estáticos de APIs públicas. Quando o origin precisa ser avaliado por request, a lógica no Route Handler fica mais clara.

Comandos de teste

Use curl para separar preflight e request real. Confirme se Access-Control-Allow-Origin corresponde exatamente ao Origin enviado e se Access-Control-Allow-Credentials: true aparece apenas para origins permitidos.

curl -i -X OPTIONS http://localhost:8787/api/messages \
  -H "Origin: http://localhost:3000" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization"

curl -i -X POST http://localhost:8787/api/messages \
  -H "Origin: http://localhost:3000" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer dev-token" \
  --data '{"text":"hello"}'

curl -i -X OPTIONS http://localhost:8787/api/messages \
  -H "Origin: https://evil.example" \
  -H "Access-Control-Request-Method: POST"

No navegador, um teste com credentials fica assim. Se credentials: "include" for usado, uma resposta CORS com wildcard será rejeitada pelo navegador.

await fetch("http://localhost:8787/api/messages", {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer dev-token",
  },
  body: JSON.stringify({ text: "hello" }),
});

Casos de uso reais

O primeiro caso é uma SPA e uma API em domínios separados. Se o React está em https://app.example.com e a API em https://api.example.com, uma allowlist explícita é necessária. Com cookies de login, revise credentials, atributos de cookie e CSRF juntos.

O segundo caso é um frontend administrativo. Você pode adicionar https://admin.example.com à allowlist, mas CORS não substitui autorização de administrador. Essa lógica fica na API.

O terceiro caso é um Cloudflare Worker como BFF ou proxy leve. O navegador chama o Worker, e o Worker chama a API upstream. A resposta do Worker para o navegador ainda precisa de CORS correto.

O quarto caso é uma API pública somente leitura. Sem cookies, Authorization ou dados privados, Access-Control-Allow-Origin: * pode ser aceitável. Se autenticação pode surgir depois, comece com allowlist.

Armadilhas específicas

ArmadilhaResultadoCorreção
Combinar * com credentials: trueNavegador bloqueia a respostaRetorne o origin explícito
Registrar https://app.example.com/A barra final impede matchGuarde https://app.example.com
Permitir só localhostPortas diferentes falhamUse http://localhost:3000
Exigir auth em OPTIONSPreflight para em 401/403Trate preflight antes da auth
Sem CORS em errosDevTools esconde o erro realAdicione headers em 4xx/5xx
CDN cacheia headers por originHeaders se misturamAdicione Vary: Origin
Tratar CORS como autorizaçãoClientes não navegador continuam chamandoImplemente auth e CSRF à parte

O MDN é claro: requests CORS com credentials não podem usar Access-Control-Allow-Origin: *. Se Claude Code gerar essa combinação, trate como bug.

Prompts de revisão para Claude Code

Review this repository's CORS configuration.
Check:
- No Access-Control-Allow-Origin: * when credentials are enabled
- Allowlist uses exact scheme/host/port matching
- OPTIONS preflight runs before authentication middleware
- 4xx/5xx responses include the required CORS headers
- Vary: Origin is present when responses vary by origin
If changes are needed, propose the smallest safe diff.
Diagnose this CORS error by cause.
Browser error:
<paste the full DevTools Console message>

curl preflight:
<paste curl -i -X OPTIONS output>

Expected origin:
https://app.example.com

Read the relevant API files and return reproduction steps, root cause, fix, and tests.
Review the Express/Fastify/Next.js/Workers CORS implementation as a security reviewer.
Focus on:
- Whether request origins are blindly reflected
- Whether localhost remains enabled in production
- Whether Authorization is allowed without proper authorization checks
- Whether cookie flows mention SameSite=None; Secure and CSRF protection
- Whether test commands separate preflight and the real request
Group findings as Critical, Must fix, and Improvement.

Use o guia de CORS do MDN como base. Para implementação, consulte Express cors middleware, @fastify/cors, exemplos CORS do Cloudflare Workers e Next.js Route Handlers. Para fluxos reutilizáveis, veja Claude Code commands.

Leia também o guia de desenvolvimento de API, o guia de headers de segurança web, o guia de Cloudflare Workers e a checklist de revisão de código.

Próximo passo

Depois de trocar os domínios de exemplo pelos seus, rode os prompts de revisão no repositório e use o guia de boas práticas de segurança com Claude Code para revisar cookies, CSRF, autorização e headers em conjunto. Em projetos de cliente ou plataformas internas, essa checklist pode virar artefato de revisão e apoiar propostas de implementação paga ou adoção de templates reutilizáveis.

Resultado ao testar na prática

No teste local de Masa, os exemplos de Express e Fastify rodaram em localhost:8787; preflight e POST de Origin: http://localhost:3000 funcionaram, enquanto https://evil.example retornou 403. Os pontos mais fáceis de esquecer foram CORS em respostas de erro e tratamento explícito de OPTIONS no Workers. O fluxo mais estável foi implementar a allowlist, rodar os curl e pedir ao Claude Code para confirmar que não existe wildcard com credentials, que Vary: Origin aparece onde precisa e que localhost não entra em produção.

#Claude Code #CORS #security #API #web development
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.