CORS (Cross-Origin Resource Sharing) Anleitung¶
Umfassende Anleitung für CORS-Policies in GAL (Gateway Abstraction Layer)
Inhaltsverzeichnis¶
- Übersicht
- Schnellstart
- Konfigurationsoptionen
- Provider-Implementierung
- Häufige Anwendungsfälle
- Sicherheits-Best-Practices
- CORS Testen
- Troubleshooting
Übersicht¶
CORS (Cross-Origin Resource Sharing) ist ein Sicherheitsmechanismus, der es Webanwendungen ermöglicht, Ressourcen von verschiedenen Ursprüngen (Origins) anzufordern. GAL bietet eine einheitliche CORS-Konfiguration für alle unterstützten Gateway-Provider.
Was ist CORS?¶
CORS ermöglicht es Browsern, Cross-Origin-Requests durchzuführen, die normalerweise durch die Same-Origin-Policy blockiert würden. Beispiel:
- Frontend:
https://app.example.com - API:
https://api.example.com
Ohne CORS würde der Browser die Requests vom Frontend zur API blockieren, da sie unterschiedliche Origins haben.
Warum ist CORS wichtig?¶
- ✅ Sicherheit: Kontrolliert, welche Domains auf deine API zugreifen dürfen
- ✅ Flexibilität: Ermöglicht moderne Frontend-Architekturen (SPA, PWA)
- ✅ Browser-Kompatibilität: Funktioniert mit allen modernen Browsern
- ✅ Credentials-Support: Ermöglicht Cookies und Authentication headers
Unterstützte Features¶
| Feature | Kong | APISIX | Traefik | Envoy | Beschreibung |
|---|---|---|---|---|---|
| Allowed Origins | ✅ | ✅ | ✅ | ✅ | Welche Origins dürfen zugreifen |
| Allowed Methods | ✅ | ✅ | ✅ | ✅ | Erlaubte HTTP-Methoden |
| Allowed Headers | ✅ | ✅ | ✅ | ✅ | Erlaubte Request-Headers |
| Exposed Headers | ✅ | ✅ | ✅ | ✅ | Headers die an Client zurückgegeben werden |
| Credentials | ✅ | ✅ | ✅ | ✅ | Cookies/Auth headers erlauben |
| Max Age | ✅ | ✅ | ✅ | ✅ | Preflight-Cache-Dauer |
Schnellstart¶
Einfache CORS-Konfiguration¶
Erlaube alle Origins (nur für Entwicklung!):
version: "1.0"
provider: kong
services:
- name: api_service
type: rest
protocol: http
upstream:
host: api.example.com
port: 8080
routes:
- path_prefix: /api
methods: [GET, POST, OPTIONS]
cors:
enabled: true
allowed_origins: ["*"]
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization]
Produktions-CORS-Konfiguration¶
Beschränke auf spezifische Origins:
routes:
- path_prefix: /api
methods: [GET, POST, OPTIONS]
cors:
enabled: true
allowed_origins:
- "https://app.example.com"
- "https://www.example.com"
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization, X-API-Key]
expose_headers: [X-Request-ID, X-RateLimit-Remaining]
allow_credentials: true
max_age: 86400 # 24 Stunden
CORS mit Authentication¶
Kombiniere CORS mit JWT-Authentication:
routes:
- path_prefix: /api/protected
methods: [GET, POST, OPTIONS]
# CORS-Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST, OPTIONS]
allowed_headers: [Content-Type, Authorization]
allow_credentials: true
# Authentication
authentication:
enabled: true
type: jwt
jwt:
issuer: "https://auth.example.com"
audience: "api.example.com"
jwks_uri: "https://auth.example.com/.well-known/jwks.json"
Konfigurationsoptionen¶
CORSPolicy Felder¶
cors:
# Aktivierung
enabled: true
# Erlaubte Origins (Domains)
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
# Oder Wildcard (NUR für Entwicklung!)
# - "*"
# Erlaubte HTTP-Methoden
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS # Wichtig für Preflight!
# Erlaubte Request-Headers
allowed_headers:
- Content-Type
- Authorization
- X-API-Key
- X-Custom-Header
# Headers die an Browser zurückgegeben werden
expose_headers:
- X-Request-ID
- X-Response-Time
- X-RateLimit-Remaining
# Credentials erlauben (Cookies, Auth headers)
allow_credentials: true
# Preflight-Cache-Dauer (in Sekunden)
max_age: 86400 # 24 Stunden
Standard-Werte¶
Wenn Felder weggelassen werden, gelten folgende Defaults:
cors:
enabled: true
allowed_origins: ["*"]
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization]
expose_headers: []
allow_credentials: false
max_age: 86400
Wildcard Origins (*)¶
⚠️ Warnung: Wildcard Origins sind unsicher für Produktion!
# ❌ NICHT in Produktion verwenden!
cors:
allowed_origins: ["*"]
allow_credentials: true # Funktioniert nicht mit "*"!
# ✅ Stattdessen spezifische Origins:
cors:
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
allow_credentials: true
Wichtig: Wenn allow_credentials: true, dann darf NICHT * als Origin verwendet werden!
Provider-Implementierung¶
Kong (cors plugin)¶
Kong verwendet das native cors Plugin:
# GAL Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST]
allow_credentials: true
# Wird zu Kong-Config:
plugins:
- name: cors
config:
origins:
- "https://app.example.com"
methods:
- GET
- POST
headers:
- Content-Type
- Authorization
credentials: true
max_age: 86400
Kong Besonderheiten: - ✅ Vollständige CORS-Unterstützung - ✅ Native Plugin-Integration - ✅ Regex-Support für Origins - ⚠️ OPTIONS-Methode muss in Route-Methoden enthalten sein
APISIX (cors plugin)¶
APISIX verwendet das cors Plugin mit komma-separiertem Format:
# GAL Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com", "https://www.example.com"]
allowed_methods: [GET, POST]
# Wird zu APISIX-Config:
{
"plugins": {
"cors": {
"allow_origins": "https://app.example.com,https://www.example.com",
"allow_methods": "GET,POST",
"allow_headers": "Content-Type,Authorization",
"allow_credential": true,
"max_age": 86400
}
}
}
APISIX Besonderheiten:
- ✅ Native CORS-Plugin
- ⚠️ Komma-separierte Listen (automatisch konvertiert)
- ⚠️ allow_credential (singular, nicht plural!)
Traefik (headers middleware)¶
Traefik implementiert CORS via Headers-Middleware:
# GAL Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST]
# Wird zu Traefik-Config:
middlewares:
api_router_0_cors:
headers:
accessControlAllowMethods:
- GET
- POST
accessControlAllowOriginList:
- "https://app.example.com"
accessControlAllowHeaders:
- Content-Type
- Authorization
accessControlAllowCredentials: true
accessControlMaxAge: 86400
Traefik Besonderheiten:
- ✅ Headers-Middleware für CORS
- ✅ accessControl* Konfiguration
- ⚠️ Middleware muss in Router referenziert werden
Envoy (native CORS policy)¶
Envoy hat native CORS-Unterstützung auf Route-Level:
# GAL Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST]
# Wird zu Envoy-Config:
routes:
- match:
prefix: /api
route:
cluster: api_cluster
cors:
allow_origin_string_match:
- exact: "https://app.example.com"
allow_methods: "GET, POST"
allow_headers: "Content-Type, Authorization"
allow_credentials: true
max_age: "86400"
Envoy Besonderheiten:
- ✅ Native CORS-Policy
- ✅ Regex-Support für Origins
- ⚠️ Wildcard * wird zu safe_regex: '.*' konvertiert
Nginx (add_header directive)¶
Nginx implementiert CORS mit add_header Directives in Location-Blöcken:
# GAL generiert Nginx-Config
location /api {
# Handle OPTIONS (Preflight) Request
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-API-Key' always;
add_header 'Access-Control-Max-Age' '86400' always;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' '0';
return 204;
}
# Handle Actual Request
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-API-Key' always;
add_header 'Access-Control-Expose-Headers' 'X-Request-ID, X-RateLimit-Remaining' always;
proxy_pass http://backend;
}
Mit OpenResty (Lua):
location /api {
access_by_lua_block {
local origin = ngx.var.http_origin
local allowed_origins = {
["https://app.example.com"] = true,
["https://admin.example.com"] = true
}
if allowed_origins[origin] then
ngx.header["Access-Control-Allow-Origin"] = origin
ngx.header["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
ngx.header["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
ngx.header["Access-Control-Allow-Credentials"] = "true"
ngx.header["Access-Control-Max-Age"] = "86400"
if ngx.var.request_method == "OPTIONS" then
ngx.exit(204)
end
end
}
proxy_pass http://backend;
}
Nginx Besonderheiten:
- ✅ Native add_header Support für CORS Headers
- ✅ if ($request_method = 'OPTIONS') für Preflight Handling
- ✅ always Flag wichtig (Header auch bei Errors)
- ✅ OpenResty/Lua für dynamische Origin-Validierung
- ⚠️ Viele Origins = Viele if-Blocks (nicht optimal)
- ⚠️ Lua-basierte Lösung empfohlen für Production
Testing:
# Preflight Request
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v \
http://nginx-host/api
# Response:
# HTTP/1.1 204 No Content
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
# Access-Control-Max-Age: 86400
HAProxy (http-response set-header)¶
HAProxy implementiert CORS mit http-response set-header Directives:
# GAL generiert HAProxy-Config
frontend api_front
bind *:80
# CORS Headers für alle Responses
http-response set-header Access-Control-Allow-Origin "https://app.example.com"
http-response set-header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
http-response set-header Access-Control-Allow-Headers "Content-Type, Authorization, X-API-Key"
http-response set-header Access-Control-Expose-Headers "X-Request-ID, X-RateLimit-Remaining"
http-response set-header Access-Control-Max-Age "86400"
# Handle OPTIONS (Preflight) Request
acl is_options method OPTIONS
http-request deny deny_status 204 if is_options
default_backend api_backend
backend api_backend
server backend1 backend.example.com:8080
Mit Multiple Origins (ACL-basiert):
frontend api_front
bind *:80
# Define allowed origins
acl origin_app hdr(origin) -i https://app.example.com
acl origin_admin hdr(origin) -i https://admin.example.com
acl valid_origin or origin_app origin_admin
# Set CORS headers only for valid origins
http-response set-header Access-Control-Allow-Origin %[req.hdr(Origin)] if valid_origin
http-response set-header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" if valid_origin
http-response set-header Access-Control-Allow-Headers "Content-Type, Authorization" if valid_origin
http-response set-header Access-Control-Allow-Credentials "true" if valid_origin
http-response set-header Access-Control-Max-Age "86400" if valid_origin
# Handle OPTIONS preflight
acl is_options method OPTIONS
http-request return status 204 if is_options valid_origin
default_backend api_backend
HAProxy Besonderheiten:
- ✅ http-response set-header für CORS Headers
- ✅ ACL-basierte Origin-Validierung (sehr performant)
- ✅ http-request return status 204 für Preflight (HAProxy 2.2+)
- ✅ %[req.hdr(Origin)] für dynamische Origin-Reflection
- ⚠️ Ältere HAProxy Versionen (<2.2): http-request deny deny_status 204 statt return
- ✅ ACL-Validierung verhindert offene CORS-Policies
Testing:
# Preflight Request
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-v \
http://haproxy-host/api
# Response:
# HTTP/1.1 204 No Content
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Max-Age: 86400
# Actual Request
curl -X GET \
-H "Origin: https://app.example.com" \
http://haproxy-host/api
# Response Headers include:
# Access-Control-Allow-Origin: https://app.example.com
HAProxy CORS Best Practices:
1. Verwende ACLs für Origin-Validierung (nicht * Wildcard)
2. Nutze %[req.hdr(Origin)] für dynamische Origin-Reflection
3. HAProxy 2.2+: Verwende http-request return status 204 für Preflight
4. Setze Access-Control-Max-Age hoch (86400 = 24h) für Performance
5. Teste immer Preflight (OPTIONS) und Actual Request separat
GCP API Gateway (OPTIONS methods)¶
GCP API Gateway implementiert CORS mit expliziten OPTIONS Methods in OpenAPI 2.0:
# GAL Konfiguration
cors:
enabled: true
allowed_origins: ["https://app.example.com", "*"]
allowed_methods: [GET, POST, PUT]
allowed_headers: [Content-Type, Authorization]
allow_credentials: false
max_age: 3600
# Wird zu OpenAPI 2.0 Config:
swagger: "2.0"
paths:
/api/users:
# Actual endpoint
get:
summary: "Get users"
responses:
200:
description: "Success"
# CORS Preflight (OPTIONS)
options:
summary: "CORS preflight"
operationId: "corsUsers"
responses:
200:
description: "CORS headers"
headers:
Access-Control-Allow-Origin:
type: "string"
description: "https://app.example.com"
Access-Control-Allow-Methods:
type: "string"
description: "GET, POST, PUT, OPTIONS"
Access-Control-Allow-Headers:
type: "string"
description: "Content-Type, Authorization"
Access-Control-Max-Age:
type: "string"
description: "3600"
Deployment:
# OpenAPI Spec mit CORS OPTIONS methods deployen
gcloud api-gateway api-configs create cors-api-config \
--api=my-api \
--openapi-spec=openapi-cors.yaml \
--project=my-gcp-project
# Gateway aktualisieren
gcloud api-gateway gateways update my-gateway \
--api=my-api \
--api-config=cors-api-config \
--location=us-central1 \
--project=my-gcp-project
CORS Testen:
# Preflight Request (OPTIONS)
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type, Authorization" \
https://my-gateway-xyz.apigateway.my-gcp-project.cloud.goog/api/users
# Erwartete Response Headers:
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Max-Age: 3600
# Actual Request mit CORS
curl -X GET \
-H "Origin: https://app.example.com" \
-H "Authorization: Bearer <JWT>" \
https://my-gateway-xyz.apigateway.my-gcp-project.cloud.goog/api/users
# Response Headers enthalten:
# Access-Control-Allow-Origin: https://app.example.com
GCP API Gateway Besonderheiten:
- ✅ CORS wird durch explizite OPTIONS Methods implementiert
- ✅ Access-Control-* Headers werden automatisch in Responses hinzugefügt
- ✅ Wildcard * für allowed_origins wird unterstützt
- ⚠️ OPTIONS Methods müssen für jeden Pfad explizit definiert werden
- ⚠️ CORS-Konfiguration erfolgt auf OpenAPI-Ebene, nicht auf Gateway-Ebene
- ✅ Preflight-Caching mit Access-Control-Max-Age wird unterstützt
- ✅ Credentials (Access-Control-Allow-Credentials) werden unterstützt
Wichtige Hinweise:
- GCP API Gateway hat keine native CORS-Konfiguration wie Kong/APISIX
- CORS wird durch OpenAPI 2.0 OPTIONS Methods implementiert
- GAL generiert automatisch OPTIONS Methods für alle Routen mit cors.enabled: true
- Für Production: Verwende spezifische Origins statt Wildcard * mit Credentials
AWS API Gateway (OPTIONS methods mit Mock Integration)¶
AWS API Gateway implementiert CORS mit expliziten OPTIONS Methods und Mock Integrations in OpenAPI 3.0:
AWS API Gateway CORS-Mechanismus:
- Mechanismus: OpenAPI 3.0 OPTIONS Methods mit x-amazon-apigateway-integration (type: mock)
- Features: Automatic OPTIONS Method Generation, Gateway Responses (DEFAULT_4XX, DEFAULT_5XX), Response Headers Mapping
- Hinweis: CORS wird durch explizite OPTIONS Methods implementiert, keine native Gateway-Level CORS-Config
Generiertes Config-Beispiel:
{
"openapi": "3.0.1",
"info": {
"title": "CORS API",
"version": "1.0.0"
},
"paths": {
"/api/users": {
"get": {
"summary": "Get users",
"responses": {
"200": {
"description": "Success",
"headers": {
"Access-Control-Allow-Origin": {
"schema": {"type": "string"}
}
}
}
},
"x-amazon-apigateway-integration": {
"type": "http_proxy",
"httpMethod": "GET",
"uri": "https://backend.example.com/api/users",
"responses": {
"default": {
"statusCode": "200",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'https://app.example.com'"
}
}
}
}
},
"options": {
"summary": "CORS preflight",
"responses": {
"200": {
"description": "CORS preflight response",
"headers": {
"Access-Control-Allow-Origin": {"schema": {"type": "string"}},
"Access-Control-Allow-Methods": {"schema": {"type": "string"}},
"Access-Control-Allow-Headers": {"schema": {"type": "string"}},
"Access-Control-Max-Age": {"schema": {"type": "string"}}
}
}
},
"x-amazon-apigateway-integration": {
"type": "mock",
"requestTemplates": {
"application/json": "{\"statusCode\": 200}"
},
"responses": {
"default": {
"statusCode": "200",
"responseParameters": {
"method.response.header.Access-Control-Allow-Origin": "'https://app.example.com'",
"method.response.header.Access-Control-Allow-Methods": "'GET,POST,PUT,DELETE,OPTIONS'",
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,X-Api-Key'",
"method.response.header.Access-Control-Max-Age": "'86400'"
}
}
}
}
}
}
}
}
Deployment:
# OpenAPI mit CORS generieren
gal generate -c config.yaml -p aws_apigateway -o api.json
# API erstellen/updaten
API_ID=$(aws apigateway import-rest-api \
--body file://api.json \
--query 'id' --output text)
# Gateway Responses für 4XX/5XX CORS Headers erstellen
aws apigateway put-gateway-response \
--rest-api-id $API_ID \
--response-type DEFAULT_4XX \
--response-parameters \
"gatewayresponse.header.Access-Control-Allow-Origin='https://app.example.com'" \
"gatewayresponse.header.Access-Control-Allow-Methods='GET,POST,PUT,DELETE,OPTIONS'" \
"gatewayresponse.header.Access-Control-Allow-Headers='Content-Type,Authorization'"
aws apigateway put-gateway-response \
--rest-api-id $API_ID \
--response-type DEFAULT_5XX \
--response-parameters \
"gatewayresponse.header.Access-Control-Allow-Origin='https://app.example.com'"
# Deployment erstellen
aws apigateway create-deployment \
--rest-api-id $API_ID \
--stage-name prod
# API URL anzeigen
echo "https://${API_ID}.execute-api.us-east-1.amazonaws.com/prod"
CORS Testen:
# Preflight Request (OPTIONS)
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type, Authorization" \
-v \
https://${API_ID}.execute-api.us-east-1.amazonaws.com/prod/api/users
# Erwartete Response Headers:
# HTTP/1.1 200 OK
# Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
# Access-Control-Allow-Headers: Content-Type,Authorization,X-Api-Key
# Access-Control-Max-Age: 86400
# Actual Request mit CORS
curl -X GET \
-H "Origin: https://app.example.com" \
-H "Authorization: Bearer <token>" \
https://${API_ID}.execute-api.us-east-1.amazonaws.com/prod/api/users
# Response Headers enthalten:
# Access-Control-Allow-Origin: https://app.example.com
Browser Preflight Ablauf:
1. Browser sendet OPTIONS Request:
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
2. API Gateway Mock Integration antwortet:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type,Authorization,X-Api-Key
Access-Control-Max-Age: 86400
3. Browser cached Preflight für 86400 Sekunden (24 Stunden)
4. Browser sendet Actual Request:
POST /api/users HTTP/1.1
Origin: https://app.example.com
Content-Type: application/json
Authorization: Bearer <token>
5. API Gateway fügt CORS Headers zu Response hinzu:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Content-Type: application/json
AWS API Gateway CORS-spezifische Features:
- ✅ OPTIONS Methods mit Mock Integration (keine Backend-Calls für Preflight)
- ✅ Gateway Responses für 4XX/5XX Error CORS Headers
- ✅ Response Parameters Mapping für dynamische CORS Headers
- ✅ Wildcard * für allowed_origins unterstützt
- ✅ Credentials (Access-Control-Allow-Credentials: true) unterstützt
- ✅ Preflight-Caching mit Access-Control-Max-Age
- ✅ CloudWatch Logs für CORS Preflight Requests
AWS-spezifische CORS-Konfigurationen:
# GAL Config für AWS CORS
services:
- name: api
routes:
- path_prefix: /api/users
methods: [GET, POST, PUT, DELETE]
cors:
enabled: true
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization, X-Api-Key]
expose_headers: [X-Request-ID, X-RateLimit-Remaining]
allow_credentials: true
max_age: 86400 # 24 Stunden
# GAL generiert automatisch:
# - OPTIONS Method für /api/users mit Mock Integration
# - Response Parameters für alle CORS Headers
# - Gateway Responses für 4XX/5XX CORS Headers
Troubleshooting:
Problem 1: CORS Headers fehlen bei 4XX/5XX Errors
# Lösung: Gateway Responses konfigurieren
aws apigateway put-gateway-response \
--rest-api-id $API_ID \
--response-type DEFAULT_4XX \
--response-parameters \
"gatewayresponse.header.Access-Control-Allow-Origin='*'"
Problem 2: Preflight Request schlägt fehl (OPTIONS 403)
# Prüfe: OPTIONS Method ist in OpenAPI definiert
# Prüfe: OPTIONS Method hat Mock Integration (type: "mock")
# Prüfe: API Key Required ist false für OPTIONS (api_key_required: false)
Problem 3: Wildcard * funktioniert nicht mit Credentials
# Falsch:
cors:
allowed_origins: ["*"]
allow_credentials: true # ❌ Nicht erlaubt!
# Richtig:
cors:
allowed_origins: ["https://app.example.com"] # ✅ Spezifische Origin
allow_credentials: true
AWS API Gateway Besonderheiten:
- ✅ CORS wird durch explizite OPTIONS Methods implementiert (wie GCP API Gateway)
- ✅ Mock Integration für OPTIONS (keine Backend-Calls, sehr schnell)
- ✅ Gateway Responses für Error CORS Headers (wichtig für Auth-Fehler!)
- ⚠️ OPTIONS Methods müssen für jeden Pfad explizit definiert werden
- ⚠️ CORS-Konfiguration erfolgt auf OpenAPI-Ebene, keine native Gateway-Config
- ⚠️ Gateway Responses müssen separat für 4XX/5XX konfiguriert werden (nicht in OpenAPI)
- ✅ Preflight-Caching reduziert OPTIONS Requests (max_age)
- ✅ Wildcard * wird unterstützt (aber nicht mit Credentials!)
Best Practices:
1. Verwende Mock Integration für OPTIONS Methods (kein Backend-Call nötig)
2. Konfiguriere Gateway Responses für 4XX/5XX CORS Headers (wichtig für Auth-Fehler!)
3. Setze max_age auf 86400 (24 Stunden) für Preflight-Caching
4. Verwende spezifische Origins statt Wildcard * mit Credentials
5. Teste Preflight mit curl -X OPTIONS -v vor Production-Deployment
6. Prüfe CloudWatch Logs für CORS-Fehler (OPTIONS 403, 401)
Häufige Anwendungsfälle¶
1. Single-Page Application (SPA)¶
Frontend und Backend auf verschiedenen Domains:
# Frontend: https://app.example.com
# Backend: https://api.example.com
services:
- name: api
routes:
- path_prefix: /api
methods: [GET, POST, PUT, DELETE, OPTIONS]
cors:
enabled: true
allowed_origins:
- "https://app.example.com"
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization]
allow_credentials: true
2. Mehrere Frontend-Domains¶
Erlaube mehrere Subdomains:
cors:
enabled: true
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
- "https://mobile.example.com"
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allow_credentials: true
3. Public API (keine Credentials)¶
Öffentliche API ohne Authentication:
cors:
enabled: true
allowed_origins: ["*"] # OK für public APIs
allowed_methods: [GET, POST, OPTIONS]
allowed_headers: [Content-Type]
allow_credentials: false # Wichtig!
max_age: 86400
4. API mit Custom Headers¶
Erlaube spezielle API-Keys im Header:
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST, OPTIONS]
allowed_headers:
- Content-Type
- Authorization
- X-API-Key # Custom Header
- X-Client-ID # Custom Header
expose_headers:
- X-RateLimit-Limit
- X-RateLimit-Remaining
- X-RateLimit-Reset
5. Entwicklung vs. Produktion¶
Verschiedene CORS-Konfigurationen:
# development.yaml
cors:
enabled: true
allowed_origins: ["*"]
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allow_credentials: false
# production.yaml
cors:
enabled: true
allowed_origins:
- "https://app.example.com"
- "https://www.example.com"
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allowed_headers: [Content-Type, Authorization]
allow_credentials: true
6. Mobile Apps¶
Erlaube spezielle Origins für mobile Apps:
cors:
enabled: true
allowed_origins:
- "https://app.example.com" # Web
- "capacitor://localhost" # Capacitor iOS
- "http://localhost" # Capacitor Android
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
allow_credentials: true
7. GraphQL API¶
CORS für GraphQL-Endpoints:
services:
- name: graphql_api
routes:
- path_prefix: /graphql
methods: [GET, POST, OPTIONS]
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, POST, OPTIONS]
allowed_headers:
- Content-Type
- Authorization
- X-Apollo-Tracing # GraphQL-spezifisch
8. WebSocket-Verbindungen¶
CORS für WebSocket-Upgrades:
services:
- name: websocket_service
routes:
- path_prefix: /ws
methods: [GET, OPTIONS] # WebSocket uses GET for upgrade
cors:
enabled: true
allowed_origins: ["https://app.example.com"]
allowed_methods: [GET, OPTIONS]
allowed_headers:
- Sec-WebSocket-Protocol
- Sec-WebSocket-Extensions
Sicherheits-Best-Practices¶
1. ❌ Niemals Wildcard mit Credentials¶
Falsch (Sicherheitslücke!):
Richtig:
2. ✅ Spezifische Origins verwenden¶
Falsch:
Richtig:
3. ✅ Minimale Header-Rechte¶
Erlaube nur benötigte Headers:
# ❌ Zu permissiv
allowed_headers: ["*"]
# ✅ Nur benötigte Headers
allowed_headers:
- Content-Type
- Authorization
- X-API-Key
4. ✅ OPTIONS-Methode nicht vergessen¶
Für Preflight-Requests:
routes:
- path_prefix: /api
methods: [GET, POST, PUT, DELETE, OPTIONS] # ✅ OPTIONS!
cors:
enabled: true
5. ✅ HTTPS für Produktion¶
Verwende immer HTTPS-Origins in Produktion:
# ❌ HTTP in Produktion
allowed_origins:
- "http://app.example.com"
# ✅ HTTPS in Produktion
allowed_origins:
- "https://app.example.com"
6. ✅ Max Age begrenzen¶
Setze sinnvolle Preflight-Cache-Dauer:
7. ✅ Expose nur benötigte Headers¶
Exponiere nur Headers die der Client wirklich braucht:
# ✅ Nur benötigte Headers
expose_headers:
- X-Request-ID
- X-RateLimit-Remaining
# ❌ Zu viele interne Headers
expose_headers:
- X-Internal-Service
- X-Database-Host # Sensible Informationen!
CORS Testen¶
Mit cURL¶
Preflight-Request (OPTIONS) testen:¶
curl -X OPTIONS http://api.example.com/api/users \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
Erwartete Response-Headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Actual Request testen:¶
Erwartete Response-Headers:
Mit JavaScript (Browser)¶
// Einfacher CORS-Test
fetch('https://api.example.com/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include' // Wenn allow_credentials: true
})
.then(response => {
console.log('CORS successful!');
console.log('Response headers:', response.headers);
return response.json();
})
.catch(error => {
console.error('CORS error:', error);
});
Mit Python Requests¶
import requests
# Test Preflight
response = requests.options(
'http://api.example.com/api/users',
headers={
'Origin': 'https://app.example.com',
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'Content-Type'
}
)
print('Preflight Response Headers:')
print(response.headers)
# Check CORS headers
assert 'Access-Control-Allow-Origin' in response.headers
assert response.headers['Access-Control-Allow-Origin'] == 'https://app.example.com'
Automatisierte Tests¶
def test_cors_headers():
"""Test CORS configuration"""
response = requests.get(
'http://api.example.com/api/users',
headers={'Origin': 'https://app.example.com'}
)
# Check allowed origin
assert 'Access-Control-Allow-Origin' in response.headers
assert response.headers['Access-Control-Allow-Origin'] == 'https://app.example.com'
# Check credentials
if 'Access-Control-Allow-Credentials' in response.headers:
assert response.headers['Access-Control-Allow-Credentials'] == 'true'
def test_cors_preflight():
"""Test CORS preflight request"""
response = requests.options(
'http://api.example.com/api/users',
headers={
'Origin': 'https://app.example.com',
'Access-Control-Request-Method': 'POST'
}
)
assert response.status_code == 200
assert 'Access-Control-Allow-Methods' in response.headers
Troubleshooting¶
Problem 1: CORS-Fehler trotz Konfiguration¶
Symptom: Browser zeigt CORS-Fehler, obwohl CORS konfiguriert ist.
Lösung: 1. ✅ OPTIONS-Methode erlauben:
-
✅ Origin exakt matchen:
-
✅ CORS vor Authentication:
Problem 2: Credentials funktionieren nicht¶
Symptom: Cookies/Auth headers werden nicht gesendet.
Lösung:
# ❌ Wildcard mit Credentials
cors:
allowed_origins: ["*"]
allow_credentials: true
# ✅ Spezifische Origin
cors:
allowed_origins: ["https://app.example.com"]
allow_credentials: true
Client-Side:
// JavaScript: credentials einschließen
fetch('https://api.example.com/api', {
credentials: 'include' // Wichtig!
})
Problem 3: Preflight-Request schlägt fehl¶
Symptom: OPTIONS-Request gibt 401/403 zurück.
Lösung:
# Authentication für OPTIONS deaktivieren
routes:
- path_prefix: /api
methods: [GET, POST, OPTIONS]
cors:
enabled: true
# CORS muss VOR Authentication kommen!
Problem 4: Custom Headers werden blockiert¶
Symptom: Custom Headers wie X-API-Key funktionieren nicht.
Lösung:
cors:
allowed_headers:
- Content-Type
- Authorization
- X-API-Key # Custom Header explizit erlauben!
Client-Side:
fetch('https://api.example.com/api', {
headers: {
'X-API-Key': 'key123' // Muss in allowed_headers sein!
}
})
Problem 5: Expose-Headers nicht sichtbar¶
Symptom: Response-Header sind im Browser nicht verfügbar.
Lösung:
Client-Side:
Problem 6: CORS funktioniert nur auf einem Provider¶
Problem: CORS funktioniert auf Kong, aber nicht auf Envoy.
Lösung: Provider-spezifische Besonderheiten prüfen:
Kong:
APISIX:
Traefik:
Envoy:
Problem 7: Max-Age funktioniert nicht¶
Symptom: Preflight-Requests werden nicht gecacht.
Lösung:
# Setze max_age explizit
cors:
max_age: 86400 # 24 Stunden
# Browser-Cache prüfen (DevTools → Network → Disable Cache)
Best Practice Checkliste¶
Vor Deployment¶
- ✅ Wildcard
*nur in Entwicklung verwenden - ✅ Spezifische Origins für Produktion
- ✅ OPTIONS-Methode in allen Routes
- ✅ HTTPS-Origins in Produktion
- ✅ allow_credentials nur mit spezifischen Origins
- ✅ Minimale allowed_headers
- ✅ Nur benötigte expose_headers
- ✅ Sinnvolle max_age (24 Stunden)
Security Review¶
- ❌ Keine Wildcard mit Credentials
- ❌ Keine sensitiven Headers exponiert
- ❌ Keine HTTP-Origins in Produktion
- ✅ CORS vor Authentication
- ✅ Rate Limiting für OPTIONS-Requests
Testing¶
- ✅ Preflight-Request testen (OPTIONS)
- ✅ Actual Request testen (GET/POST)
- ✅ Mit Credentials testen
- ✅ Custom Headers testen
- ✅ Verschiedene Origins testen
- ✅ Browser DevTools prüfen
Zusammenfassung¶
CORS in GAL ermöglicht:
✅ Einheitliche Konfiguration für alle Provider ✅ Vollständige CORS-Unterstützung (Origins, Methods, Headers, Credentials) ✅ Einfache Integration mit Authentication und Rate Limiting ✅ Provider-Abstraktion - schreibe einmal, deploye überall
Nächste Schritte: - Siehe AUTHENTICATION.md für CORS + Auth - Siehe HEADERS.md für zusätzliche Header-Manipulation - Siehe examples/cors-example.yaml für vollständige Beispiele
Hilfe benötigt? - Probleme melden: https://github.com/pt9912/x-gal/issues - Dokumentation: https://docs.gal.dev - Beispiele: examples/