API Gateway CORS-Fehler beheben: Console-Einstellungen und Lambda-Response-Header

Ein CORS-Fehler im Browser ist einer der frustrierendsten Einstiegspunkte in die AWS-Welt — das Frontend sendet eine Preflight-Anfrage, bekommt keine gültige Antwort, und der Browser blockiert den eigentlichen Request, bevor er überhaupt die Lambda-Funktion erreicht. Der Fehler liegt dabei selten an einer einzigen Stelle: API Gateway CORS-Konfiguration und Lambda-Response-Header müssen beide korrekt gesetzt sein, sonst schlägt die Integration still fehl.

TL;DR — API Gateway CORS auf einen Blick

SchichtWas konfiguriert werden mussWo
API Gateway (Mock/Proxy)OPTIONS-Methode + CORS-Header in Gateway-ResponseConsole → Resource → 'Enable CORS'
Lambda-FunktionAccess-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods im Response-BodyLambda-Code (jede Response)
Lambda Proxy IntegrationAPI Gateway leitet Headers 1:1 durch — Lambda muss alle CORS-Header selbst setzenLambda-Code
Non-Proxy IntegrationAPI Gateway kann Headers via Mapping Templates injizierenIntegration Response in Console

Wie CORS bei API Gateway funktioniert

Bevor irgendjemand anfängt, in der Console Checkboxen zu setzen: CORS ist kein API-Gateway-Feature, sondern ein Browser-Sicherheitsmechanismus. Der Browser sendet bei Cross-Origin-Requests zuerst einen Preflight-Request (HTTP OPTIONS), um zu prüfen, ob der Server die Anfrage erlaubt. Erst wenn der Server mit den richtigen Headern antwortet, sendet der Browser den eigentlichen Request.

Bei API Gateway gibt es zwei grundlegend verschiedene Integrationsmodi, die sich in der CORS-Behandlung stark unterscheiden:

  • Lambda Proxy Integration: API Gateway leitet Request und Response unverändert durch. Lambda ist vollständig verantwortlich für alle Response-Header — inklusive CORS-Header.
  • Lambda Non-Proxy Integration (benutzerdefinierte Integration): API Gateway kann Response-Header über Mapping Templates und Integration Responses manipulieren. CORS-Header können hier auf Gateway-Ebene gesetzt werden.

Die 'Enable CORS'-Schaltfläche in der Console konfiguriert primär die OPTIONS-Methode mit einer Mock-Integration und setzt Gateway-Responses. Bei Lambda Proxy Integration reicht das allein nicht aus — Lambda muss die Header trotzdem selbst zurückgeben.

sequenceDiagram participant B as Browser participant AG as API Gateway participant L as Lambda B->>AG: OPTIONS /items
(Preflight) AG-->>B: 200 OK
Access-Control-Allow-Origin: * Note over B,AG: Preflight erfolgreich B->>AG: POST /items
(eigentlicher Request) AG->>L: Event weiterleiten L-->>AG: Response mit
CORS-Headern AG-->>B: 200 OK
Access-Control-Allow-Origin: * Note over B,L: Browser akzeptiert Response
  1. Browser → OPTIONS-Preflight: Der Browser sendet vor jedem Cross-Origin-Request einen OPTIONS-Request an API Gateway.
  2. API Gateway → Mock-Response: Die via 'Enable CORS' konfigurierte OPTIONS-Methode antwortet direkt mit den erlaubten Headern — ohne Lambda aufzurufen.
  3. Browser → eigentlicher Request: Nur wenn der Preflight erfolgreich war, sendet der Browser den GET/POST/etc.-Request.
  4. Lambda → Response mit CORS-Headern: Lambda muss in jeder Response die CORS-Header mitsenden, sonst blockiert der Browser die Antwort trotz erfolgreichem Preflight.

Schritt 1: 'Enable CORS' in der API Gateway Console aktivieren

Die Console-Option konfiguriert die OPTIONS-Methode und die Gateway-Default-Responses. Das ist der notwendige erste Schritt, aber bei Lambda Proxy Integration nicht der letzte.

  1. In der API Gateway Console die gewünschte REST API öffnen.
  2. Im linken Navigationsbereich die Ressource auswählen (z.B. /items).
  3. Im Menü ActionsEnable CORS auswählen.
  4. Im Dialog die folgenden Felder prüfen und anpassen:
    • Access-Control-Allow-Headers: Standardmäßig Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
    • Access-Control-Allow-Origin: Für Entwicklung *, für Produktion den spezifischen Origin (z.B. https://example.com)
    • Access-Control-Allow-Methods: Alle Methoden auswählen, die die Ressource unterstützt
  5. Enable CORS and replace existing CORS headers bestätigen.
  6. Die API deployen — ohne Deployment hat die Änderung keine Wirkung.

Nach dem Aktivieren erstellt die Console automatisch eine OPTIONS-Methode mit Mock-Integration auf der ausgewählten Ressource. Diese Mock-Integration antwortet auf Preflight-Requests direkt, ohne Lambda aufzurufen.

# Deployment via CLI nach CORS-Konfiguration
aws apigateway create-deployment \
  --rest-api-id a1b2c3d4e5 \
  --stage-name prod \
  --region us-east-1
# Aktuellen CORS-Status einer Methode prüfen
aws apigateway get-method \
  --rest-api-id a1b2c3d4e5 \
  --resource-id xyz789 \
  --http-method OPTIONS \
  --region us-east-1

Schritt 2: Lambda-Response-Header korrekt setzen (Lambda Proxy Integration)

Hier scheitern die meisten — der Preflight funktioniert, aber der eigentliche Request wird trotzdem blockiert. Der Grund: Bei Lambda Proxy Integration übergibt API Gateway die Lambda-Response unverändert an den Browser. Wenn Lambda keine CORS-Header zurückgibt, sieht der Browser keine Erlaubnis, auch wenn die OPTIONS-Methode korrekt konfiguriert ist.

Lambda muss in jeder Response — Erfolg und Fehler — die CORS-Header mitsenden.

🔽 Python-Beispiel: Lambda-Response mit CORS-Headern
import json

def lambda_handler(event, context):
    # CORS-Header müssen in jeder Response enthalten sein
    cors_headers = {
        'Access-Control-Allow-Origin': 'https://example.com',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization',
        'Access-Control-Allow-Methods': 'GET,POST,OPTIONS'
    }

    try:
        # Eigentliche Geschäftslogik
        result = {'message': 'Erfolg'}

        return {
            'statusCode': 200,
            'headers': cors_headers,
            'body': json.dumps(result)
        }

    except Exception as e:
        # Auch Fehler-Responses brauchen CORS-Header
        return {
            'statusCode': 500,
            'headers': cors_headers,
            'body': json.dumps({'error': str(e)})
        }
🔽 Node.js-Beispiel: Lambda-Response mit CORS-Headern
exports.handler = async (event) => {
    const corsHeaders = {
        'Access-Control-Allow-Origin': 'https://example.com',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization',
        'Access-Control-Allow-Methods': 'GET,POST,OPTIONS'
    };

    try {
        const result = { message: 'Erfolg' };

        return {
            statusCode: 200,
            headers: corsHeaders,
            body: JSON.stringify(result)
        };
    } catch (err) {
        return {
            statusCode: 500,
            headers: corsHeaders,
            body: JSON.stringify({ error: err.message })
        };
    }
};

Schritt 3: Gateway Responses für Fehler auf API-Ebene konfigurieren

Es gibt einen wenig bekannten Fallstrick: Wenn API Gateway selbst einen Fehler zurückgibt — z.B. 403 bei fehlgeschlagener Autorisierung oder 429 bei Rate Limiting — generiert API Gateway die Response, nicht Lambda. Diese Gateway-eigenen Fehler-Responses enthalten standardmäßig keine CORS-Header. Das bedeutet: Der Browser sieht einen CORS-Fehler, obwohl das eigentliche Problem ein Autorisierungsfehler ist.

graph TD A[Browser sendet Request] --> B{API Gateway
Autorisierung} B -->|Fehler z.B. 403| C[Gateway Response
ohne CORS-Header] B -->|Erfolg| D[Lambda aufrufen] C --> E[Browser: CORS-Fehler
obwohl Ursache Auth-Fehler] D --> F{Lambda Response} F -->|CORS-Header fehlen| G[Browser: CORS-Fehler] F -->|CORS-Header vorhanden| H[Browser: Erfolg] style E fill:#ff6b6b,color:#fff style G fill:#ff6b6b,color:#fff style H fill:#51cf66,color:#fff style C fill:#ffa94d,color:#fff

Gateway Responses für CORS konfigurieren:

# Alle verfügbaren Gateway Response Typen anzeigen
aws apigateway get-gateway-responses \
  --rest-api-id a1b2c3d4e5 \
  --region us-east-1
# CORS-Header für DEFAULT_4XX Gateway Response setzen
aws apigateway put-gateway-response \
  --rest-api-id a1b2c3d4e5 \
  --response-type DEFAULT_4XX \
  --response-parameters '{"gatewayresponse.header.Access-Control-Allow-Origin": "'\''https://example.com'\''", "gatewayresponse.header.Access-Control-Allow-Headers": "'\''Content-Type,Authorization'\''"}' \
  --region us-east-1
# CORS-Header für DEFAULT_5XX Gateway Response setzen
aws apigateway put-gateway-response \
  --rest-api-id a1b2c3d4e5 \
  --response-type DEFAULT_5XX \
  --response-parameters '{"gatewayresponse.header.Access-Control-Allow-Origin": "'\''https://example.com'\''", "gatewayresponse.header.Access-Control-Allow-Headers": "'\''Content-Type,Authorization'\''"}' \
  --region us-east-1

In der Console ist das unter API Gateway → [API-Name] → Gateway Responses erreichbar. Dort können für jeden Response-Typ individuelle Header gesetzt werden.

Diagnose: Warum der CORS-Fehler trotz Konfiguration bestehen bleibt

Das klassische Muster: CORS in der Console aktiviert, API deployed, Browser zeigt immer noch einen CORS-Fehler. Meistens liegt es an einem dieser drei Punkte.

Symptom → Fehldiagnose → tatsächliche Ursache

Der Browser meldet 'Access to fetch at ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present'. Die erste Reaktion ist, die CORS-Einstellung in der Console nochmals zu prüfen — alles sieht korrekt aus. Der Fehler liegt aber darin, dass die API nach der CORS-Konfiguration nicht neu deployed wurde. API Gateway-Änderungen sind erst nach einem expliziten Deployment auf einem Stage wirksam. Die Console zeigt die Konfiguration korrekt an, aber der Stage läuft noch auf dem alten Deployment.

# Deployment-Historie eines Stage prüfen
aws apigateway get-stage \
  --rest-api-id a1b2c3d4e5 \
  --stage-name prod \
  --region us-east-1

Das Feld lastUpdatedDate im Output zeigt, wann der Stage zuletzt aktualisiert wurde. Liegt dieses Datum vor der CORS-Konfiguration, fehlt das Deployment.

Checkliste für hartnäckige CORS-Fehler

  • API nicht deployed: Nach jeder Änderung muss die API explizit auf dem Stage deployed werden.
  • Lambda gibt keine CORS-Header zurück: Bei Proxy Integration reicht die Gateway-Konfiguration allein nicht aus — Lambda-Code prüfen.
  • Origin-Mismatch: Access-Control-Allow-Origin: * funktioniert nicht bei Requests mit credentials: 'include'. In diesem Fall muss der spezifische Origin gesetzt werden.
  • Gateway Responses fehlen: Wenn API Gateway selbst einen 4xx/5xx zurückgibt (z.B. Authorizer-Fehler), fehlen CORS-Header in der Response, wenn Gateway Responses nicht konfiguriert sind.
  • HTTP API vs. REST API: HTTP APIs (v2) haben eine andere CORS-Konfiguration als REST APIs (v1). Die Einstellungen sind nicht übertragbar.
# OPTIONS-Preflight manuell testen (simuliert Browser-Verhalten)
curl -v -X OPTIONS \
  https://a1b2c3d4e5.execute-api.us-east-1.amazonaws.com/prod/items \
  -H 'Origin: https://example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: Content-Type'

Der curl-Output zeigt, welche CORS-Header API Gateway tatsächlich zurückgibt. Wenn Access-Control-Allow-Origin fehlt oder nicht mit dem Origin übereinstimmt, ist das der Beweis, dass die Gateway-Konfiguration nicht greift.

CORS bei HTTP API (API Gateway v2)

HTTP APIs haben eine dedizierte CORS-Konfiguration, die sich von REST APIs unterscheidet. Bei HTTP APIs kann CORS direkt in der API-Konfiguration gesetzt werden — ohne manuelle OPTIONS-Methoden oder Mock-Integrationen.

# CORS-Konfiguration einer HTTP API anzeigen
aws apigatewayv2 get-api \
  --api-id a1b2c3d4e5 \
  --region us-east-1
# CORS für eine HTTP API konfigurieren
aws apigatewayv2 update-api \
  --api-id a1b2c3d4e5 \
  --cors-configuration AllowOrigins='https://example.com',AllowMethods='GET,POST',AllowHeaders='Content-Type,Authorization' \
  --region us-east-1

Auch bei HTTP APIs mit Lambda Proxy Integration muss Lambda die CORS-Header in der Response zurückgeben, wenn die HTTP API CORS-Konfiguration nicht alle Fälle abdeckt. Die HTTP API CORS-Konfiguration behandelt Preflight-Requests automatisch, aber die eigentlichen Response-Header werden von Lambda geliefert.

IAM-Berechtigungen für API Gateway CORS-Konfiguration

Für die Konfiguration von CORS über die CLI oder Console sind folgende IAM-Berechtigungen erforderlich:

🔽 IAM-Policy für API Gateway CORS-Konfiguration
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "apigateway:GET",
        "apigateway:PUT",
        "apigateway:POST",
        "apigateway:PATCH",
        "apigateway:DELETE"
      ],
      "Resource": [
        "arn:aws:apigateway:us-east-1::/restapis/a1b2c3d4e5/*"
      ]
    }
  ]
}
CORS-Konfiguration ist wie ein Türsteher-Protokoll: Der Browser (Gast) fragt zuerst beim Eingang (OPTIONS/Preflight), ob er mit seiner Herkunft (Origin) eingelassen wird. Erst nach der Bestätigung betritt er das Lokal (eigentlicher Request). Wenn der Türsteher (API Gateway) die richtigen Antworten gibt, aber der Kellner (Lambda) keine Ausweise akzeptiert, kommt der Gast trotzdem nicht rein.

Wrap-up: API Gateway CORS-Fehler systematisch beheben

CORS-Fehler bei API Gateway haben fast immer eine von drei Ursachen: fehlende Lambda-Response-Header bei Proxy Integration, fehlendes Deployment nach Konfigurationsänderungen, oder fehlende Gateway Responses für API-seitige Fehler. Die 'Enable CORS'-Funktion in der Console ist ein guter Startpunkt, aber kein vollständiger Fix — besonders bei Lambda Proxy Integration.

Weiterführende Ressourcen:

Glossar

BegriffBedeutung
CORS (Cross-Origin Resource Sharing)Browser-Sicherheitsmechanismus, der Cross-Origin-HTTP-Requests kontrolliert. Wird durch HTTP-Header zwischen Server und Browser ausgehandelt.
Preflight-RequestAutomatischer OPTIONS-Request des Browsers vor dem eigentlichen Cross-Origin-Request, um die Erlaubnis des Servers zu prüfen.
Lambda Proxy IntegrationAPI Gateway-Integrationsmodus, bei dem Request und Response unverändert zwischen API Gateway und Lambda weitergeleitet werden. Lambda ist vollständig für Response-Header verantwortlich.
Gateway ResponseVon API Gateway selbst generierte HTTP-Response für bestimmte Fehlerzustände (z.B. 403, 429, 500), bevor Lambda aufgerufen wird.
Stage DeploymentExpliziter Schritt in API Gateway, der Konfigurationsänderungen auf einem benannten Stage (z.B. 'prod') veröffentlicht. Ohne Deployment sind Änderungen nicht wirksam.

Kommentare

Beliebte Posts aus diesem Blog

EC2 ohne Internetzugang im eigenen VPC – Internet Gateway und Route Table korrekt einrichten

LSI vs. GSI in DynamoDB: Den richtigen Sekundärindex wählen

ElastiCache Redis einsetzen: Wann ein Caching-Layer deine RDS-Datenbank entlastet