CORS-Fehler gehören zu den frustrierendsten Problemen in der Webentwicklung. Sie sehen die gefürchtete rote Konsolenmeldung, Ihr API-Aufruf schlägt fehl, und nichts scheint zu funktionieren. Dieser Guide hilft Ihnen, CORS genau zu verstehen, Ihren spezifischen Fehler zu diagnostizieren und mit der richtigen Lösung zu beheben.
Was ist CORS? (30-Sekunden-Erklärung)
Cross-Origin Resource Sharing (CORS) ist eine Browser-Sicherheitsfunktion, die Webseiten daran hindert, Anfragen an eine andere Domain als die sendende zu stellen. Wenn Ihr Frontend unter http://localhost:3000 Daten von http://api.example.com abrufen möchte, prüft der Browser, ob der API-Server diese Cross-Origin-Anfrage über spezielle HTTP-Header explizit erlaubt.
Wie CORS funktioniert: Der Ablauf
┌─────────────────┐ ┌─────────────────────┐
│ Browser │ │ API Server │
│ (Frontend) │ │ (Backend) │
│ │ │ │
│ http://localhost │ ──────> │ https://api.example │
│ :3000 │ fetch │ .com │
│ │ │ │
└─────────────────┘ └─────────────────────┘
│
┌───────────────┴───────────────┐
│ │
CORS Headers No CORS Headers
Present? Present?
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Response sent │ │ Browser BLOCKS │
│ to JavaScript │ │ the response │
└─────────────────┘ └──────────────────┘Wenn CORS korrekt konfiguriert ist, antwortet der Server mit Headern, die dem Browser sagen: "Ja, ich erlaube Anfragen von diesem Ursprung."
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Content-Type: application/json
{"data": "success"}Wenn CORS NICHT konfiguriert ist, blockiert der Browser die Antwort (auch wenn der Server die Anfrage erfolgreich verarbeitet hat).
HTTP/1.1 200 OK
Content-Type: application/json
# No Access-Control-Allow-Origin header!
{"data": "success"} <-- Browser received this but BLOCKS JavaScript from reading itDie 5 häufigsten CORS-Fehlermeldungen
Fehler 1: Kein Access-Control-Allow-Origin Header
No 'Access-Control-Allow-Origin' header is present on the requested resource.Bedeutung: Der Server hat keinen Access-Control-Allow-Origin Header in seiner Antwort. Dies ist der häufigste CORS-Fehler.
Fehler 2: Wildcard mit Anmeldedaten
The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when credentials mode is 'include'.Bedeutung: Sie senden Cookies oder Authorization-Header, aber der Server antwortet mit *. Bei Anmeldedaten muss der Server den genauen Ursprung angeben.
Fehler 3: Preflight-Anfrage fehlgeschlagen
Response to preflight request doesn't pass access control check.Bedeutung: Der Browser hat eine OPTIONS-Anfrage (Preflight) gesendet und der Server hat diese nicht korrekt behandelt.
Fehler 4: Methode nicht erlaubt
Method PUT is not allowed by Access-Control-Allow-Methods.Bedeutung: Der Access-Control-Allow-Methods Header des Servers enthält die verwendete HTTP-Methode nicht.
Fehler 5: Header nicht erlaubt
Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers.Bedeutung: Der Server hat den benutzerdefinierten Header nicht in Access-Control-Allow-Headers erlaubt.
Diagnose-Flussdiagramm: Wo liegt das Problem?
Verwenden Sie diesen Entscheidungsbaum, um schnell zu identifizieren, ob Ihr CORS-Problem ein Frontend-, Backend- oder Proxy-Problem ist:
Schritt 1: Öffnen Sie DevTools > Network-Tab. Sehen Sie eine OPTIONS-Anfrage vor Ihrer eigentlichen Anfrage?
JA -> Der Browser sendet eine Preflight-Anfrage. Prüfen Sie, ob die OPTIONS-Antwort Status 200 hat und CORS-Header enthält.
NEIN -> Dies ist eine einfache Anfrage. Das Problem sind fehlende CORS-Header in der Serverantwort.
Schritt 2: Erwähnt die Fehlermeldung "preflight"?
JA -> Backend-Problem. Ihr Server muss OPTIONS-Anfragen behandeln.
NEIN -> Prüfen Sie, ob Sie Anmeldedaten verwenden. Wenn ja, können Sie keinen Wildcard (*) verwenden.
Schritt 3: Funktioniert die API bei direktem Aufruf (curl, Postman)?
JA -> Es ist ein CORS-Problem (nur Browser).
NEIN -> Kein CORS-Problem. Die API selbst hat ein Problem.
Zusammenfassung: 90% der CORS-Fehler werden durch korrekte Header-Konfiguration auf dem Backend-Server gelöst.
Server-seitige Fixes nach Framework
Express.js (Node.js)
// Method 1: Using the cors package (recommended)
npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// Allow specific origins
app.use(cors({
origin: ['http://localhost:3000', 'https://yoursite.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // Cache preflight for 24 hours
}));
// Method 2: Manual middleware
app.use((req, res, next) => {
const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
// Handle preflight
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Max-Age', '86400');
return res.status(204).end();
}
next();
});Django (Python)
# Install django-cors-headers
pip install django-cors-headers
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # Must be FIRST
'django.middleware.common.CommonMiddleware',
...
]
# Option 1: Allow specific origins
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'https://yoursite.com',
]
# Option 2: Allow all origins (dev only!)
# CORS_ALLOW_ALL_ORIGINS = True
# Allow credentials (cookies, auth headers)
CORS_ALLOW_CREDENTIALS = True
# Allowed headers
CORS_ALLOW_HEADERS = [
'content-type',
'authorization',
'x-requested-with',
]
# Allowed methods
CORS_ALLOW_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
]Flask (Python)
# Install flask-cors
pip install flask-cors
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Option 1: Allow specific origins
CORS(app, origins=['http://localhost:3000', 'https://yoursite.com'],
supports_credentials=True,
allow_headers=['Content-Type', 'Authorization'],
methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
# Option 2: Manual approach
@app.after_request
def after_request(response):
origin = request.headers.get('Origin')
allowed = ['http://localhost:3000', 'https://yoursite.com']
if origin in allowed:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
response.headers['Access-Control-Allow-Credentials'] = 'true'
return responseGo (net/http)
package main
import (
"net/http"
"strings"
)
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
allowedOrigins := map[string]bool{
"http://localhost:3000": true,
"https://yoursite.com": true,
}
origin := r.Header.Get("Origin")
if allowedOrigins[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods",
strings.Join([]string{"GET","POST","PUT","DELETE","PATCH"}, ","))
w.Header().Set("Access-Control-Allow-Headers",
"Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Max-Age", "86400")
}
// Handle preflight
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/data", handleData)
handler := corsMiddleware(mux)
http.ListenAndServe(":8080", handler)
}Nginx-Konfiguration
server {
listen 80;
server_name api.example.com;
# CORS headers for all locations
set $cors_origin "";
if ($http_origin ~* "^https?://(localhost:3000|yoursite\.com)$") {
set $cors_origin $http_origin;
}
location /api/ {
# CORS headers
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400 always;
# Handle preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain';
return 204;
}
proxy_pass http://backend:8080;
}
}Reverse Proxy als CORS-Middleware
Wenn Sie die Backend-API nicht ändern können (z.B. Drittanbieter-API), verwenden Sie einen Reverse Proxy, um CORS-Header hinzuzufügen.
Nginx Reverse Proxy
# Nginx as CORS proxy for a third-party API
server {
listen 80;
server_name cors-proxy.yoursite.com;
location /proxy/ {
# Add CORS headers
add_header 'Access-Control-Allow-Origin' 'https://yoursite.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://yoursite.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
# Strip /proxy/ prefix and forward to third-party API
rewrite ^/proxy/(.*) /$1 break;
proxy_pass https://third-party-api.com;
proxy_set_header Host third-party-api.com;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Usage: fetch('https://cors-proxy.yoursite.com/proxy/endpoint')Apache Reverse Proxy
# Apache as CORS proxy
<VirtualHost *:80>
ServerName cors-proxy.yoursite.com
# Enable required modules
# a2enmod proxy proxy_http headers rewrite
<Location "/proxy/">
Header always set Access-Control-Allow-Origin "https://yoursite.com"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
Header always set Access-Control-Allow-Credentials "true"
# Handle preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]
# Proxy to backend
ProxyPass "http://backend:8080/"
ProxyPassReverse "http://backend:8080/"
</Location>
</VirtualHost>Nur-Entwicklung-Lösungen
Diese Lösungen sind nur für die lokale Entwicklung. Niemals in der Produktion verwenden.
Vite Proxy
Vites eingebauter Proxy leitet Anfragen vom Dev-Server an die API weiter und vermeidet CORS komplett:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
// Proxy /api requests to backend
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
// Optional: rewrite path
// rewrite: (path) => path.replace(/^\/api/, ''),
},
// Proxy with WebSocket support
'/ws': {
target: 'ws://localhost:8080',
ws: true,
},
},
},
});
// In your code, use relative URLs:
// fetch('/api/users') instead of fetch('http://localhost:8080/api/users')Webpack devServer Proxy
Create React App und andere webpack-basierte Setups unterstützen einen ähnlichen Proxy:
// package.json (Create React App)
{
"proxy": "http://localhost:8080"
}
// Or for more control, create src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true,
})
);
};
// webpack.config.js (manual setup)
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
};Next.js Rewrites
Next.js kann API-Anfragen über seinen eigenen Server mit Rewrites proxyen:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:8080/api/:path*',
},
];
},
};
module.exports = nextConfig;
// In your code:
// fetch('/api/users') -- proxied to http://localhost:8080/api/usersHäufige Fallstricke und Grenzfälle
Fallstrick 1: Wildcard (*) mit Anmeldedaten
Wenn das Frontend Anmeldedaten sendet, KANN der Server NICHT * verwenden. Er muss den genauen Ursprung zurückgeben:
FALSCH (schlägt mit Anmeldedaten fehl):
// Frontend
fetch('https://api.example.com/data', {
credentials: 'include' // Sending cookies
});
// Backend response header:
Access-Control-Allow-Origin: * // FAILS! Cannot use * with credentialsRICHTIG (dynamischer Ursprung für Anmeldedaten):
// Backend (Express.js example)
app.use((req, res, next) => {
// Echo back the exact origin
const origin = req.headers.origin;
const allowedOrigins = ['http://localhost:3000', 'https://yoursite.com'];
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin); // Exact origin
}
res.setHeader('Access-Control-Allow-Credentials', 'true');
next();
});Fallstrick 2: Fehlender OPTIONS-Handler (Preflight)
Nicht-einfache Anfragen lösen einen Preflight-OPTIONS-Request aus. Wenn der Server 404 oder 405 für OPTIONS zurückgibt, scheitert CORS.
| Trigger | Example |
|---|---|
| Custom headers | Authorization, X-Request-ID, X-API-Key |
| Non-simple methods | PUT, DELETE, PATCH |
| Non-simple Content-Type | application/json, application/xml |
| ReadableStream body | Streaming request body |
// Ensure your server handles OPTIONS for all API routes:
app.options('*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
res.status(204).end();
});Fallstrick 3: Cookies und SameSite
Cross-Origin Cookies benötigen drei Bedingungen:
// 1. Frontend: include credentials
fetch('https://api.example.com/data', {
credentials: 'include'
});
// 2. Backend: allow credentials + exact origin
res.setHeader('Access-Control-Allow-Origin', 'https://yoursite.com'); // NOT *
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 3. Cookie must have correct attributes
Set-Cookie: session=abc123; SameSite=None; Secure; HttpOnly; Path=/
// IMPORTANT: SameSite=None requires Secure (HTTPS).
// This means cross-origin cookies do NOT work on HTTP (localhost).
// For local dev, use a proxy instead of cross-origin cookies.Fallstrick 4: Preflight-Antworten cachen
Preflight-Anfragen können langsam sein. Verwenden Sie Access-Control-Max-Age zum Cachen:
// Server response header:
Access-Control-Max-Age: 86400 // Cache preflight for 24 hours
// Without this header, the browser sends a preflight OPTIONS
// request before EVERY non-simple request, adding latency.
// With caching, the browser remembers the CORS permissions
// and skips the preflight for subsequent requests.Hinweis: Chrome begrenzt auf 7200 Sekunden (2 Stunden). Firefox auf 86400 (24 Stunden).
Fallstrick 5: CORS bei Weiterleitungen
Wenn Ihr API-Endpoint weiterleitet (301/302), müssen CORS-Header in BEIDEN Antworten vorhanden sein. Vermeiden Sie Weiterleitungen in API-Endpoints.
// PROBLEM: API endpoint redirects
// GET https://api.example.com/users -> 301 -> https://api.example.com/v2/users
// CORS headers must be on BOTH responses!
// SOLUTION: Avoid redirects in API endpoints
// Option 1: Update the frontend URL
fetch('https://api.example.com/v2/users'); // Use final URL directly
// Option 2: Make the redirect include CORS headers
// (requires server configuration on both the old and new endpoint)Schnellreferenz: CORS-Header
| Header | Zweck | Beispielwert |
|---|---|---|
| Access-Control-Allow-Origin | Which origins can access the resource | https://yoursite.com or * |
| Access-Control-Allow-Methods | Which HTTP methods are allowed | GET, POST, PUT, DELETE |
| Access-Control-Allow-Headers | Which request headers are allowed | Content-Type, Authorization |
| Access-Control-Allow-Credentials | Whether cookies/auth can be sent | true |
| Access-Control-Max-Age | How long to cache preflight (seconds) | 86400 |
| Access-Control-Expose-Headers | Which response headers JS can read | X-Total-Count, X-Request-ID |
Häufig gestellte Fragen
Kann man CORS im Browser deaktivieren?
Technisch ja, durch Starten von Chrome mit --disable-web-security, aber das ist extrem gefährlich. Für die Entwicklung verwenden Sie einen Proxy (Vite, webpack).
Warum funktioniert meine API in Postman, aber nicht im Browser?
CORS ist ein reiner Browser-Sicherheitsmechanismus. Postman, curl und Server-zu-Server-Anfragen erzwingen kein CORS.
Ist CORS ein Frontend- oder Backend-Fix?
Fast immer Backend. Der Server muss die richtigen CORS-Header senden. Das Frontend kann CORS nicht umgehen. Die einzige Frontend-Lösung ist ein Proxy.
Sollte man Access-Control-Allow-Origin: * in der Produktion verwenden?
Nur für wirklich öffentliche APIs ohne Cookies oder Authentifizierung. Bei Anmeldedaten müssen genaue Ursprünge angegeben werden.
Was ist eine Preflight-Anfrage?
Eine automatische OPTIONS-Anfrage, die der Browser vor der eigentlichen Anfrage sendet. Tritt bei benutzerdefinierten Headern, nicht-einfachen Methoden oder speziellem Content-Type auf.
Wie behebt man CORS bei WebSocket-Verbindungen?
WebSocket-Verbindungen folgen nicht den CORS-Regeln. Der Server muss den Origin-Header manuell validieren. Bei CORS-Fehlern liegt das Problem meist beim initialen HTTP-Handshake-Endpoint.