14

I'm trying to build an application with the AWS CDK and if I were to build an application by hand using the AWS Console, I normally would enable CORS in API gateway.

Even though I can export the swagger out of API Gateway and have found numerous options to generate a Mock endpoint for the OPTIONS method I don't see how to do this with the CDK. Currently I was trying:

const apigw             = require('@aws-cdk/aws-apigateway');

where:

var api                 = new apigw.RestApi(this, 'testApi');

and defining the OPTIONS method like:

const testResource   = api.root.addResource('testresource');

var mock = new apigw.MockIntegration({
                    type: "Mock",
                    methodResponses: [
                            {
                                    statusCode: "200",
                                    responseParameters : {
                                            "Access-Control-Allow-Headers" : "string",
                                            "Access-Control-Allow-Methods" : "string",
                                            "Access-Control-Allow-Origin" : "string"
                                    }
                            }
                    ],
                    integrationResponses: [
                            {
                                    statusCode: "200",
                                    responseParameters: {
                                            "Access-Control-Allow-Headers" :  "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
                                            "Access-Control-Allow-Origin" : "'*'",
                                            "Access-Control-Allow-Methods" : "'GET,POST,OPTIONS'"
                                    }
                            }
                    ],
                    requestTemplates: {
                            "application/json": "{\"statusCode\": 200}"
                    }
            });

            testResource.addMethod('OPTIONS', mock);

But this doesn't deploy. The error message I get from the cloudformation stack deploy when I run "cdk deploy" is:

Invalid mapping expression specified: Validation Result: warnings : [], errors : [Invalid mapping expression specified: Access-Control-Allow-Origin] (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException;

Ideas?

Ivan Kluzak
  • 655
  • 2
  • 7
  • 11

4 Answers4

32

The recent change has made enabling CORS simpler:

const restApi = new apigw.RestApi(this, `api`, {
  defaultCorsPreflightOptions: {
    allowOrigins: apigw.Cors.ALL_ORIGINS
  }
});
ceilfors
  • 2,617
  • 23
  • 33
  • 2
    This doesn't work if you have `binaryMediaTypes` enabled. – Radu Diță May 07 '21 at 11:32
  • 6
    This works well for preflight requests. But does not add the necessary CORS headers to any requests after the preflight. So you will still need to add the CORS headers to, for example, the POST request response headers. – Kyle Higginson Apr 10 '22 at 10:21
  • @RaduDiță do you mind explaining why this does not work for binary data? We were just stumbling over this and your comment with a service that had binarymedia defined and was ignoring our cors configuration we sent with CDK – Sebastian Schürmann Apr 22 '22 at 09:30
  • @KyleHigginson Thank you. I was so confused because my preflight request succeeded yet my GET request failed with a CORS error. Once I added the CORS headers to the lambda GET handler, the request succeeded. https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html – mrwnt10 Jan 08 '23 at 07:56
  • Still a bit confused - I get the preflight stuff, and CDK has made adding that a breeze. But whether its API GW or CDK in general... is there a way to avoid having to tweak each of your lambda's responses to add the CORS related response headers? Can this not be done in one place as well? Editing the preflight is nice, but if its not in sync with your actual lambda responses then its actually bad, isn't it? I'm clearly missing something. – mobob Aug 22 '23 at 16:30
7

Haven't tested this myself, but based on this answer, it seems like you would need to use a slightly different set of keys when you define your MOCK integration:

const api = new apigw.RestApi(this, 'api');

const method = api.root.addMethod('OPTIONS', new apigw.MockIntegration({
  integrationResponses: [
    {
      statusCode: "200",
      responseParameters: {
        "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
        "method.response.header.Access-Control-Allow-Methods": "'GET,POST,OPTIONS'",
        "method.response.header.Access-Control-Allow-Origin": "'*'"
      },
      responseTemplates: {
        "application/json": ""
      }
    }
  ],
  passthroughBehavior: apigw.PassthroughBehavior.Never,
  requestTemplates: {
    "application/json": "{\"statusCode\": 200}"
  },
}));

// since "methodResponses" is not supported by apigw.Method (https://github.com/awslabs/aws-cdk/issues/905)
// we will need to use an escape hatch to override the property

const methodResource = method.findChild('Resource') as apigw.cloudformation.MethodResource;
methodResource.propertyOverrides.methodResponses = [
  {
    statusCode: '200',
    responseModels: {
      'application/json': 'Empty'
    },
    responseParameters: {
      'method.response.header.Access-Control-Allow-Headers': true,
      'method.response.header.Access-Control-Allow-Methods': true,
      'method.response.header.Access-Control-Allow-Origin': true
    }
  }
]

Would be useful to be able to enable CORS using a more friendly API.

Elad Ben-Israel
  • 824
  • 4
  • 6
7

Edit: With updates to CDK it is no longer necessary to use an escape hatch. Please see other answers as they are much cleaner.

Original answer:

This version, originally created by Heitor Vital on github uses only native constructs.

export function addCorsOptions(apiResource: apigateway.IResource) {
    apiResource.addMethod('OPTIONS', new apigateway.MockIntegration({
        integrationResponses: [{
        statusCode: '200',
        responseParameters: {
            'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
            'method.response.header.Access-Control-Allow-Origin': "'*'",
            'method.response.header.Access-Control-Allow-Credentials': "'false'",
            'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,PUT,POST,DELETE'",
        },
        }],
        passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
        requestTemplates: {
        "application/json": "{\"statusCode\": 200}"
        },
    }), {
        methodResponses: [{
        statusCode: '200',
        responseParameters: {
            'method.response.header.Access-Control-Allow-Headers': true,
            'method.response.header.Access-Control-Allow-Methods': true,
            'method.response.header.Access-Control-Allow-Credentials': true,
            'method.response.header.Access-Control-Allow-Origin': true,
        },  
        }]
    })
}

I also ported the same code to python using his version as a guidepost.

def add_cors_options(api_resource):
    """Add response to OPTIONS to enable CORS on an API resource."""
    mock = apigateway.MockIntegration(
        integration_responses=[{
            'statusCode': '200',
            'responseParameters': {
                'method.response.header.Access-Control-Allow-Headers':
                    "'Content-Type,\
                      X-Amz-Date,\
                      Authorization,\
                      X-Api-Key,\
                      X-Amz-Security-Token,X-Amz-User-Agent'",
                'method.response.header.Access-Control-Allow-Origin': "'*'",
                'method.response.header.Access-Control-Allow-Credentials':
                    "'false'",
                'method.response.header.Access-Control-Allow-Methods':
                    "'OPTIONS,\
                      GET,\
                      PUT,\
                      POST,\
                      DELETE'",
            }
        }],
        passthrough_behavior=apigateway.PassthroughBehavior.NEVER,
        request_templates={
            "application/json": "{\"statusCode\": 200}"
        }
    )
    method_response = apigateway.MethodResponse(
        status_code='200',
        response_parameters={
            'method.response.header.Access-Control-Allow-Headers': True,
            'method.response.header.Access-Control-Allow-Methods': True,
            'method.response.header.Access-Control-Allow-Credentials': True,
            'method.response.header.Access-Control-Allow-Origin': True
        }
    )
    api_resource.add_method(
        'OPTIONS',
        integration=mock,
        method_responses=[method_response]
    )
Dax Hurley
  • 71
  • 1
  • 4
1

BACKGROUND

I came across this answer while trying to implement the aws_api_gateway_integration_response in Terraform and accidentally came across the solution.

PROBLEM

I was getting this error message:

Invalid mapping expression specified: Validation Result: warnings : [], errors : [Invalid mapping expression specified: POST,GET,OPTIONS]

In the aws_api_gateway_integration_response resource I had the response_parameter key as:

response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token"
    "method.response.header.Access-Control-Allow-Origin" = "*"
    "method.response.header.Access-Control-Allow-Methods" = "POST,GET,OPTIONS"
    # "method.response.header.Access-Control-Allow-Credentials" = "false"
  }

I thought everything was fine as I assumed the double quotes were all that Terraform needed. However, that was not the case.

SOLUTION

I had to add a single quote around the values inside the double quote. Like this:

response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
    "method.response.header.Access-Control-Allow-Origin" = "'*'"
    "method.response.header.Access-Control-Allow-Methods" = "'POST,GET,OPTIONS'"
    # "method.response.header.Access-Control-Allow-Credentials" = "false"
  }
codeinaire
  • 1,682
  • 1
  • 13
  • 26