Troubleshooting AWS API Gateway & CORS issues

The problem

Setting CORS while using AWS API Gateway can be confusing as the Enable CORS option in the Action menu doesn't work out of the box. The problem occurs during the pre-flight call that fail as it is. A solution to this issue is simply to create a MOCK endpoint for such API and make sure the response headers are set appropriately.

Mock integrations are a quick and easy way to enable CORS

A mock integration can be implemented either on the console or better yet using a Cloudformation template.

Header mappings via console

These are some simple steps (far from perfect) I have used to enable CORS using the console

1. Add the OPTIONS method to each endpoint. Configure the endpoint as a MOCK integration.

2. In the Method Response (Left bottom) block add Status 200 and the corresponding Response Headers

Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin

3. In the Method Execution (Right Bottom) block, use the 200 response status created in step 2 and map headers as shown below.

Access-Control-Allow-Headers: 'Access-Control-Allow-Origin, Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' 

Access-Control-Allow-Methods: 'OPTIONS'

Access-Control-Allow-Origin: '*'

Mapping Template: application/json
{
    statusCode: 200,
    headers: {
        "Access-Control-Allow-Origin": "*"
    },
        body: ''
};

Some sources advise explicitly setting the header in the mapping template too.

4. Save your changes and deploy API using the actions menu.

As mentioned in step 1, this should be repeated for each CORS-enabled endpoint, that includes GET requests.

You also need to add the Access-Control-Allow-Origin: '*' mapping to your GET, POST, PUT, DELETE methods (steps 2, 3)

Add more response codes (4xx, 5xx) by repeating steps 2 and 3 🔄

A better way: Cloudformation

A more IaaC-compliant way to achieve this is by writing some good old yaml as a Cloudformation template that can be use to define an integration for the specific resource. The definition is for the OPTIONS method and include status code, the response parameters (specifically the allow origin header), and the Mock type. The example below requires identifiers for the the API and the Resource and allows the POST method only.

OptionsMethod:
  Type: AWS::ApiGateway::Method
  Properties:
    AuthorizationType: NONE
    RestApiId:
      Ref: ApiIdentifier
    ResourceId:
      Ref: ResourceIdentifier
    HttpMethod: OPTIONS
    Integration:
      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: "'POST,OPTIONS'"
          method.response.header.Access-Control-Allow-Origin: "'*'"
        ResponseTemplates:
          application/json: ''
      PassthroughBehavior: WHEN_NO_MATCH
      RequestTemplates:
        application/json: '{"statusCode": 200}'
      Type: MOCK
    MethodResponses:
    - StatusCode: 200
      ResponseModels:
        application/json: 'Empty'
      ResponseParameters:
          method.response.header.Access-Control-Allow-Headers: false
          method.response.header.Access-Control-Allow-Methods: false
          method.response.header.Access-Control-Allow-Origin: false

The last three lines in this yaml specify whether the headers are required.

It seems a bit over complicated way to achieve something this simple but at least it seems consistent with the way API Gateway works elsewhere.