Use Cases (更新: 2026/6/2)

Claude Code CORS 配置完整指南:安全处理跨源 API

用 Claude Code 安全配置 CORS:preflight、credentials、origin allowlist、测试命令与审查提示。

Claude Code CORS 配置完整指南:安全处理跨源 API

用 Claude Code 正确配置 CORS

前端跑在 localhost:3000,API 跑在 localhost:8787,浏览器就可能报 CORS 错误。很多人第一反应是加上 Access-Control-Allow-Origin: *,但只要 API 使用 Cookie、Authorization header 或后台管理页面,这个做法就会变得危险。

CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器用来限制跨源读取响应的机制。origin 由 scheme、host、port 组成。https://app.example.comhttps://api.example.comhttp://localhost:3000http://localhost:5173 都是不同 origin。

本文把 CORS 配置拆成 Claude Code 也容易审查的步骤。你会看到 Express、Fastify、Cloudflare Workers、Next.js Route Handler 的可复制代码,以及 preflight、credentials、origin allowlist、测试命令和 Claude Code 审查提示。

最重要的一点是:CORS 不是认证。它只控制浏览器中的 JavaScript 能不能读取跨源响应,不能阻止 curl、服务端请求或未授权用户。API 认证、授权、CSRF、防刷和安全响应头仍然要单独设计。

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

写代码前先决定什么

让 Claude Code 生成代码前,先明确下面四件事。需求越含糊,越容易生成只适合 demo 的宽松配置。

项目示例注意点
允许的 originhttps://app.example.com, https://admin.example.com不包含路径,也不要末尾斜杠
是否发送凭证Cookie, Authorization headerCookie 场景还要确认 SameSite=None; Secure
允许的方法GET,POST,PUT,PATCH,DELETE,OPTIONS只开放实际使用的方法
允许的请求头Content-Type,Authorization,X-Request-ID必须与 preflight 请求匹配

preflight 是浏览器在真正请求前发出的权限确认。JSON POSTAuthorizationPUTDELETE 或自定义 header 通常会先触发 OPTIONS。如果响应中没有匹配的 Access-Control-Allow-MethodsAccess-Control-Allow-Headers,浏览器不会发送真正的请求。

Express 配置

下面的例子假设使用 Node.js 20 或以上。Express 官方的 cors middleware 允许给 origin 传入函数,因此可以按请求检查 allowlist。因为这里支持 credentials,所以只反射允许的 origin,并启用 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");
});

生产环境请设置 NODE_ENV=production,并且只在 allowedOrigins 中保留真实业务域名。没有 Origin header 的请求通常不是浏览器 CORS 请求,这里允许通过;但 API key、JWT 和用户权限仍然要由认证中间件检查。

Fastify 配置

Fastify 使用 @fastify/cors。官方 README 支持用布尔值、字符串、数组、正则或函数配置 origin,但 Set 完全匹配更容易审查。除非有强理由,不要用过宽的正则。

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" });

Fastify 中插件和 hook 的顺序很关键。如果认证 hook 在 CORS 插件之前拒绝 OPTIONS,浏览器就不会发送真正请求。让 Claude Code 审查时,要明确要求它检查插件注册顺序。

Cloudflare Workers 配置

Cloudflare Workers 直接使用标准 Fetch API。需要显式处理 OPTIONS,并在成功和错误响应中都返回 CORS header。响应会因 origin 不同而变化时,加上 Vary: 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 });
  },
};

Workers 最常见的错误是只给成功响应加 header。OPTIONS、401、403、500 忘记加 CORS header 时,DevTools 可能只显示 CORS 失败,而真正的应用错误被隐藏。

Next.js Route Handler 配置

App Router 中的 app/api/.../route.ts 使用 Web 标准的 RequestResponse。Next.js 文档展示了如何给响应添加 CORS header;如果 API 使用 credentials,就不要用 *,而要用 allowlist。

// 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 });
}

next.config.jsheaders() 适合静态、公开的 API header。需要按 origin 判断的认证 API,更推荐在 Route Handler 内处理,审查起来也更直接。

测试命令

先用 curl 把 preflight 和真正请求分开验证。重点看 Access-Control-Allow-Origin 是否与请求的 Origin 完全一致,以及 Access-Control-Allow-Credentials: true 是否只对允许的 origin 返回。

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"

浏览器侧可以这样检查 credentials。只要使用 credentials: "include",通配符 CORS 响应就会被浏览器拒绝。

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" }),
});

实际用例

第一个用例是 SPA 与 API 分域部署。React 在 https://app.example.com,API 在 https://api.example.com 时,需要明确 allowlist。如果使用登录 Cookie,要同时检查 credentials、Cookie 属性和 CSRF。

第二个用例是后台管理页面。可以把 https://admin.example.com 加入 allowlist,但不要把 CORS 当成管理员权限判断。权限仍然必须在 API 代码中实现。

第三个用例是把 Cloudflare Worker 当作 BFF 或轻量代理。浏览器访问 Worker,Worker 再访问上游 API。最终返回给浏览器的 Worker 响应仍然需要正确的 CORS header。

第四个用例是公开只读 API。如果没有 Cookie、没有 Authorization、没有私有数据,Access-Control-Allow-Origin: * 可以接受。若未来可能加入认证,从一开始就使用 allowlist 更稳。

具体踩坑点

踩坑点结果修正
*credentials: true 同时使用浏览器阻止响应返回明确的 origin
注册 https://app.example.com/末尾斜杠导致不匹配只保存 https://app.example.com
只允许 localhost端口不同会失败写成 http://localhost:3000
OPTIONS 也要求认证preflight 在 401/403 停住在认证前处理 preflight
错误响应没有 CORSDevTools 看不到真正错误4xx/5xx 也加 CORS header
CDN 缓存了按 origin 变化的 header不同 origin header 混用Vary: Origin
把 CORS 当授权非浏览器客户端仍可调用单独实现认证、授权和 CSRF

MDN 明确说明,带 credentials 的 CORS 请求不能使用 Access-Control-Allow-Origin: *。如果 Claude Code 生成了这个组合,就应视为需要修正的安全问题。

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.

官方资料与站内链接

规格层面建议以 MDN CORS 指南 为基准。实现层面可参考 Express cors middleware@fastify/corsCloudflare Workers CORS 示例Next.js Route Handlers。Claude Code 的复用工作流可看 Claude Code commands

建议搭配阅读 API 开发指南Web 安全响应头Cloudflare Workers 指南代码审查流程清单

下一步

把示例域名替换成自己的域名后,用上面的审查提示跑一遍仓库,再用 Claude Code 安全最佳实践 一起检查 Cookie、CSRF、授权和安全响应头。如果你在做客户项目或内部平台,这份清单可以直接变成评审资料,也方便后续引入付费实施支持或复用模板。

实际试用结果

Masa 在本地把 Express 和 Fastify 示例跑在 localhost:8787,确认 Origin: http://localhost:3000 的 preflight 与 POST 成功,而 https://evil.example 返回 403。最容易漏掉的是错误响应的 CORS header,以及 Workers 中对 OPTIONS 的显式处理。最稳定的流程是先实现 allowlist,再跑 curl 检查,最后让 Claude Code 确认没有 * 加 credentials、需要的地方有 Vary: Origin,并且 localhost 不会进入生产环境。

#Claude Code #CORS #security #API #web development
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。