CORS-Konfiguration mit Claude Code: sichere Cross-Origin-APIs
CORS mit Claude Code sicher konfigurieren: Preflight, Credentials, Origin-Allowlist, Tests und Review-Prompts.
CORS mit Claude Code korrekt konfigurieren
Ein Frontend auf localhost:3000 und eine API auf localhost:8787 reichen aus, damit der Browser einen CORS-Fehler zeigt. Die schnelle Lösung wirkt oft wie Access-Control-Allow-Origin: *, aber diese Einstellung wird gefährlich, sobald Cookies, Authorization-Header oder ein Admin-Bereich im Spiel sind.
CORS, Cross-Origin Resource Sharing, ist der Mechanismus, mit dem ein Server festlegt, welche anderen Origins seine Antworten aus Browser-JavaScript lesen dürfen. Eine Origin besteht aus Scheme, Host und Port. https://app.example.com, https://api.example.com, http://localhost:3000 und http://localhost:5173 sind verschiedene Origins.
Dieser Leitfaden zerlegt die Konfiguration in überprüfbare Entscheidungen, damit Claude Code hilfreich bleibt, ohne Sicherheitsannahmen zu verstecken. Enthalten sind kopierbare Beispiele für Express, Fastify, Cloudflare Workers und Next.js Route Handler sowie Preflight, Credentials, Origin-Allowlist, Testbefehle und Review-Prompts.
Der wichtigste Punkt: CORS ist keine Authentifizierung. Es steuert nur, ob JavaScript im Browser eine Cross-Origin-Antwort lesen darf. Es blockiert weder curl noch Server-zu-Server-Aufrufe oder unberechtigte Benutzer. Authentifizierung, Autorisierung, CSRF, Rate Limiting und Sicherheitsheader bleiben separate Kontrollen.
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
Entscheidungen vor dem Code
Legen Sie diese Werte fest, bevor Claude Code Middleware schreibt. Unklare Anforderungen führen leicht zu Demo-Konfigurationen, die für Produktion zu offen sind.
| Entscheidung | Beispiel | Worauf achten |
|---|---|---|
| Erlaubte Origins | https://app.example.com, https://admin.example.com | Keine Pfade und kein Slash am Ende |
| Credentials | Cookie, Authorization header | Bei Cookies auch SameSite=None; Secure prüfen |
| Methoden | GET,POST,PUT,PATCH,DELETE,OPTIONS | Nur tatsächlich genutzte Methoden erlauben |
| Header | Content-Type,Authorization,X-Request-ID | Müssen zum Preflight passen |
Preflight ist die Berechtigungsprüfung des Browsers vor der echten Anfrage. Bei JSON-POST, Authorization, PUT, DELETE und vielen Custom Headers sendet der Browser zuerst OPTIONS. Ohne passende Access-Control-Allow-Methods und Access-Control-Allow-Headers wird die echte Anfrage nicht gesendet.
Express-Konfiguration
Das Beispiel setzt Node.js 20 oder neuer voraus. Das offizielle Express-cors-Middleware kann für origin eine Funktion verwenden, sodass jede Anfrage gegen eine Allowlist geprüft wird. Da die API Credentials unterstützt, werden nur erlaubte Origins reflektiert und credentials: true gesetzt.
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");
});
In Produktion sollte NODE_ENV=production gesetzt sein, und allowedOrigins sollte nur echte Domains enthalten. Anfragen ohne Origin-Header sind keine Browser-CORS-Anfragen; dieses Beispiel lässt sie deshalb zu. API Keys, JWTs und Benutzerrechte gehören weiterhin in die normale Authentifizierung.
Fastify-Konfiguration
Fastify nutzt @fastify/cors. Das offizielle README erlaubt Boolean, String, Array, RegExp und Function für origin, aber exakte Set-Prüfung ist leichter zu auditieren. Breite reguläre Ausdrücke sollten nur mit gutem Grund verwendet werden.
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" });
In Fastify ist die Reihenfolge von Plugins und Hooks wichtig. Wenn ein Auth-Hook OPTIONS ablehnt, bevor das CORS-Plugin antwortet, sendet der Browser die echte Anfrage nie. Lassen Sie Claude Code daher auch die Registrierungsreihenfolge prüfen.
Cloudflare-Workers-Konfiguration
Cloudflare Workers verwenden die standardisierte Fetch API. Behandeln Sie OPTIONS explizit, fügen Sie CORS-Header sowohl bei Erfolg als auch bei Fehlern hinzu, und nutzen Sie Vary: Origin, wenn die Antwort je nach Origin variiert.
// 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 });
},
};
Der häufigste Workers-Fehler ist, Header nur im Erfolgsfall zu setzen. Wenn OPTIONS, 401, 403 oder 500 keine CORS-Header enthalten, zeigt DevTools oft nur einen CORS-Fehler und verdeckt den eigentlichen Anwendungsfehler.
Next.js Route Handler
Mit dem App Router nutzt app/api/.../route.ts die Web-Objekte Request und Response. Die Next.js-Dokumentation zeigt CORS-Header auf Responses; für APIs mit Credentials sollte eine Allowlist statt * verwendet werden.
// 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() in next.config.js eignet sich für statische Header öffentlicher APIs. Wenn die Origin pro Request geprüft werden muss, ist die Route-Handler-Logik klarer.
Testbefehle
Trennen Sie mit curl Preflight und echte Anfrage. Prüfen Sie, ob Access-Control-Allow-Origin exakt zur gesendeten Origin passt und ob Access-Control-Allow-Credentials: true nur für erlaubte Origins erscheint.
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"
Im Browser sieht ein Test mit Credentials so aus. Sobald credentials: "include" verwendet wird, lehnt der Browser eine Wildcard-CORS-Antwort ab.
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" }),
});
Praktische Use Cases
Der erste Fall ist eine SPA und eine API auf getrennten Domains. Wenn React auf https://app.example.com läuft und die API auf https://api.example.com, brauchen Sie eine explizite Allowlist. Bei Login-Cookies müssen credentials, Cookie-Attribute und CSRF gemeinsam geprüft werden.
Der zweite Fall ist ein Admin-Frontend. https://admin.example.com kann in die Allowlist, ersetzt aber keine Admin-Autorisierung. Diese Prüfung gehört in die API.
Der dritte Fall ist ein Cloudflare Worker als BFF oder leichter Proxy. Der Browser ruft den Worker auf, und der Worker ruft die Upstream-API auf. Die Antwort des Workers an den Browser braucht trotzdem korrekte CORS-Header.
Der vierte Fall ist eine öffentliche Read-only-API. Ohne Cookies, ohne Authorization und ohne private Daten kann Access-Control-Allow-Origin: * akzeptabel sein. Wenn später Authentifizierung geplant ist, starten Sie besser direkt mit einer Allowlist.
Konkrete Fallstricke
| Fallstrick | Ergebnis | Korrektur |
|---|---|---|
* mit credentials: true kombinieren | Browser blockiert die Antwort | Explizite Origin zurückgeben |
https://app.example.com/ speichern | Slash am Ende passt nicht | Nur https://app.example.com speichern |
Nur localhost erlauben | Andere Ports schlagen fehl | http://localhost:3000 schreiben |
Auth für OPTIONS verlangen | Preflight stoppt bei 401/403 | Preflight vor Auth behandeln |
| CORS auf Fehlern vergessen | DevTools versteckt den echten Fehler | Header auch auf 4xx/5xx setzen |
| CDN cached origin-spezifische Header | Header vermischen sich | Vary: Origin setzen |
| CORS als Autorisierung behandeln | Nicht-Browser-Clients rufen weiter auf | Auth, Autorisierung und CSRF separat bauen |
MDN ist eindeutig: CORS-Anfragen mit Credentials dürfen Access-Control-Allow-Origin: * nicht verwenden. Wenn Claude Code diese Kombination erzeugt, ist das ein Bug.
Review-Prompts für 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.
Referenzen und interne Links
Nutzen Sie den MDN CORS Guide als Grundlage. Für Implementierungen siehe Express cors middleware, @fastify/cors, Cloudflare Workers CORS examples und Next.js Route Handlers. Für wiederverwendbare Claude-Code-Abläufe ist Claude Code commands hilfreich.
Lesen Sie außerdem den API-Entwicklungsleitfaden, den Leitfaden zu Web-Sicherheitsheadern, den Cloudflare-Workers-Leitfaden und die Code-Review-Checkliste.
Nächster Schritt
Ersetzen Sie die Beispieldomains durch Ihre eigenen, führen Sie die Review-Prompts im Repository aus und prüfen Sie anschließend mit den Claude Code Security Best Practices Cookies, CSRF, Autorisierung und Header gemeinsam. In Kundenprojekten oder internen Plattformteams kann diese Checkliste als Review-Artefakt dienen und bezahlte Implementierungshilfe oder wiederverwendbare Templates unterstützen.
Ergebnis des Praxistests
In Masas lokalem Test liefen die Express- und Fastify-Beispiele auf localhost:8787; Preflight und POST von Origin: http://localhost:3000 funktionierten, während https://evil.example 403 zurückgab. Am leichtesten übersieht man CORS-Header auf Fehlerantworten und die explizite OPTIONS-Behandlung in Workers. Der stabilste Ablauf war: Allowlist implementieren, curl-Checks ausführen und Claude Code prüfen lassen, dass keine Wildcard-Credentials-Kombination existiert, Vary: Origin dort gesetzt ist, wo es nötig ist, und localhost nicht in Produktion landet.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.