CloudFormation API Gateway CORS issue access to XMLHttpRequest blocked

I'm trying to use CloudFormation to create an API Gateway but I have CORS issue with it.

Error on the front-end:

POST https://<>.execute-api.us-east-1.amazonaws.com/prod/<> 500
new:1 Access to XMLHttpRequest at '<>' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • The API is created without any issue and I even double check every single page on the console against the working API and find no differences in their Method Request, Integration Request, Integration Response and Method Response for all the methods (including the OPTIONS).
  • If I remove the resources created by the template and create them manually in the same API gateway then my code works as expected. I've tested with the localhost, front-end code in S3 bucket and PostMan, so I can verify that my front-end code, lambda functions and database are working correctly.
  • I understand that people have had this issue before but I haven't been able to find any answer that solves my issue.

  • Here's my template.

  • Please note that the "method.response.header.Access-Control-Allow-Origin": false actually creates the API with the same settings as the working one.

  • I also use the code from the correct answer for this question.

  • Yes, my OPTIONS request has the "Access-Control-Allow-Origin" header.

Update

Following dannymac's answer below. I got these:

  • I added console.log(event.requestContext); to my Lambda function (written in Node.js).
  • There are logs for Lambda when I test the function.
2019-06-27T20:07:03.118Z    462b93b2-9d4b-4ed3-bc04-f966fcd034cf    Debug CORS issue. Request ID:
2019-06-27T20:07:03.118Z    462b93b2-9d4b-4ed3-bc04-f966fcd034cf    undefined
  • It looks like there is no event.requestContext.
  • I selected Enable CloudWatch Logs-INFO and Enable Detailed CloudWatch Metrics with CloudWatch log role ARN*:arn:aws:iam::<ID>:role/ApiGatewayCloudWatchLogsRole (it's a role created by AWS) in the API Gateway settings.
  • However, there is no CloudWatch log for the API Gateway. There's a default log in CloudWatch - Log Groups: /aws/apigateway/welcome
Time (UTC +00:00)
2019-06-27
19:50:55
Cloudwatch logs enabled for API Gateway
  • It looks like the CloudWatch log didn't pick up the test from API Gateway.
  • This is what I got from testing the GET method in my API Gateway:
Response Body
{
  "message": "Internal server error"
}
Response Headers
{}
Logs
Execution log for request 10d90173-9919-11e9-82e1-dd33dda3b9df
Thu Jun 27 20:20:54 UTC 2019 : Starting execution for request: 10d90173-9919-11e9-82e1-dd33dda3b9df
Thu Jun 27 20:20:54 UTC 2019 : HTTP Method: GET, Resource Path: /notes
Thu Jun 27 20:20:54 UTC 2019 : Method request path: {}
Thu Jun 27 20:20:54 UTC 2019 : Method request query string: {userid=<ID>}
Thu Jun 27 20:20:54 UTC 2019 : Method request headers: {}
Thu Jun 27 20:20:54 UTC 2019 : Method request body before transformations: 
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request URI: https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:770402430649:function:test-api-gateway-2-LambdaFunction-1XDONAN3QIY9I/invocations
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request headers: {x-amzn-lambda-integration-tag=... [TRUNCATED]
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request body after transformations: {"resource":"/notes","path":"/notes","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"userid":"<USERID>"},"multiValueQueryStringParameters":{"userid":["<USERID>"]},"pathParameters":null,"stageVariables":null,"requestContext":{"path":"/notes","accountId":"<ID>"...,"identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"<ARN>","apiKeyId":"test-invoke-api-key-id","userAgent":..."test [TRUNCATED]
Thu Jun 27 20:20:54 UTC 2019 : Sending request to https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:<ID>:function:test-api-gateway-2-LambdaFunction-<STRING>/invocations
Thu Jun 27 20:20:54 UTC 2019 : Received response. Status: 403, Integration latency: 6 ms
Thu Jun 27 20:20:54 UTC 2019 : Endpoint response headers: {Date=Thu, 27 Jun 2019 20:20:54 GMT, Content-Length=130, Connection=keep-alive, x-amzn-RequestId=<ID>}
Thu Jun 27 20:20:54 UTC 2019 : Endpoint response body before transformations: <AccessDeniedException>
  <Message>Unable to determine service/operation name to be authorized</Message>
</AccessDeniedException>

Thu Jun 27 20:20:54 UTC 2019 : Lambda invocation failed with status: 403. Lambda request id: feb22917-0dea-4f91-a274-fb6b85a69121
Thu Jun 27 20:20:54 UTC 2019 : Execution failed due to configuration error: 
Thu Jun 27 20:20:54 UTC 2019 : Method completed with status: 500
  • I've also exported both the working and not working API Gateway in Swagger 2. The only difference is:
// working one:
"x-amazon-apigateway-any-method": {
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "name": "noteid",
            "in": "path",
            "required": true,
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "200 response",
            "schema": {
              "$ref": "#/definitions/Empty"
            }
          }
        },
        "security": [
          {
            "mobile-notes-api-authorizer": []
          }
        ]
      }
// not working one:
"x-amazon-apigateway-any-method": {
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 response",
            "schema": {
              "$ref": "#/definitions/Empty"
            }
          }
        },
        "security": [
          {
            "test-api-gateway-2-authorizer": []
          }
        ]
      }
  • They both have:
"headers": {
              "Access-Control-Allow-Origin": {
                "type": "string"
              },
              "Access-Control-Allow-Methods": {
                "type": "string"
              },
              "Access-Control-Allow-Headers": {
                "type": "string"
              }
            }
  • I've tried to use the Swagger template in the Body of my API Gateway before but unable to solve the invalid authorizer issue.

Answers 1

  • I've figured out the issue. There are 2 main things:

    1. The IntegrationHttpMethod for Lambda must be POST. I found the answer here.
    2. The template didn't have AWS::Lambda::Permission that allows API Gateway to invoke Lambda function. With the template, when you use AWS::Lambda::Permission, it will show the API as a trigger of your Lambda function. However, if you manually create the API Gateway and link it with your Lambda function, it won't show API Gateway as a trigger but it still works.

    So for the template I posted above, I needed to add these for it to work:

    "LambdaPermission": {
                "Type": "AWS::Lambda::Permission",
                "Description": "Permission for API GateWay to invoke Lambda.",
                "Properties": {
                    "Action": "lambda:invokeFunction",
                    "FunctionName": {
                        "Fn::GetAtt": [
                            "LambdaFunction",
                            "Arn"
                        ]
                    },
                    "Principal": "apigateway.amazonaws.com",
                    "SourceArn": {
                        "Fn::Join": [
                            "",
                            [
                                "arn:aws:execute-api:",
                                {
                                    "Ref": "AWS::Region"
                                },
                                ":",
                                {
                                    "Ref": "AWS::AccountId"
                                },
                                ":",
                                {
                                    "Ref": "ApiGateway"
                                },
                                "/*"
                            ]
                        ]
                    }
                }
            },
    

    And edit method ANY to look like this

    "methodNotesANY": {
                "Type": "AWS::ApiGateway::Method",
                "DependsOn": "LambdaPermission",
                "Properties": {
                    "AuthorizationType": "COGNITO_USER_POOLS",
                    "AuthorizerId": {
                        "Ref": "GatewayAuthorizer"
                    },
                    "RestApiId": {
                        "Ref": "ApiGateway"
                    },
                    "ResourceId": {
                        "Ref": "resourceNotes"
                    },
                    "HttpMethod": "ANY",
                    "Integration": {
                        "Type": "AWS_PROXY",
                        "IntegrationHttpMethod": "POST",
                        "Uri": {
                            "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations"
                        },
                        "IntegrationResponses": [{
                            "StatusCode": "200"
                        }]
                    },
                    "MethodResponses": [{
                        "ResponseModels": {
                            "application/json": "Empty"
                        },
                        "StatusCode": "200"
                    }]
                }
            },
    

Related Articles