3

I have a sample template, which is creating an AWS Lambda function and an API Gateway,
The issue that I'm facing is the template is able to enable CORS, but it seems to be wrong as on calls from a frontend application still receive the CORS error.

Following is the template -

AWSTemplateFormatVersion: '2010-09-09'

Description: AWS API Gateway with a Lambda Integration

Parameters:
  CorsOrigin:
    Type: String
    Default: "'*'"
  CorsHeaders:
    Type: String
    Default: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
  CorsMethods:
    Type: String
    Default: "'OPTIONS,POST'"

Resources:
  BusinessLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          def handler(event, context):
            response = {
              'isBase64Encoded': False,
              'statusCode': 200,
              'headers': {},
              'multiValueHeaders': {},
              'body': 'Hello, World!'
            }
            return response
      Description: AWS Lambda function
      FunctionName: 'businessLambda'
      Handler: index.handler
      MemorySize: 128
      Role: 'arn:aws:iam::awsAccountId:role/service-role/TestRole'
      Runtime: python3.7
      Timeout: 15

  ApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      ApiKeySourceType: HEADER
      Description: An API Gateway with a Lambda Integration
      EndpointConfiguration:
        Types:
          - EDGE
      Name: api-gw

  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: 'lambda'
      RestApiId: !Ref ApiGatewayRestApi

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      ApiKeyRequired: false
      AuthorizationType: NONE
      HttpMethod: POST
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: "POST"
        Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${!stageVariables.lambdaAlias}/invocations'
        IntegrationResponses:
          - StatusCode: 200
            ResponseTemplates:
              application/json: $input.json('$')
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: !Ref CorsHeaders
              method.response.header.Access-Control-Allow-Methods: !Ref CorsMethods
              method.response.header.Access-Control-Allow-Origin: !Ref CorsOrigin
        RequestTemplates:
          application/json: $input.json('$')
      MethodResponses:
        - 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
          StatusCode: '200'
      OperationName: 'lambda'
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ApiGatewayRestApi

  APIGatewayOptionsMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ApiGatewayRestApi
      AuthorizationType: NONE
      HttpMethod: OPTIONS
      Integration:
        Type: MOCK
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: !Ref CorsHeaders
              method.response.header.Access-Control-Allow-Methods: !Ref CorsMethods
              method.response.header.Access-Control-Allow-Origin: !Ref CorsOrigin
            ResponseTemplates:
              application/json: ''
            StatusCode: '200'
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - 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
          StatusCode: '200'

  ApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref ApiGatewayDeployment
      Description: API GW Stage dev
      RestApiId: !Ref ApiGatewayRestApi
      StageName: 'dev'
      Variables: 
        'lambdaAlias' : 'businessLambda'

  GWLambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref BusinessLambda
      Principal: "apigateway.amazonaws.com"
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/*/POST/lambda"

  ApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: APIGatewayOptionsMethod
    Properties:
      Description: Lambda API Deployment
      RestApiId: !Ref ApiGatewayRestApi

Outputs:
  test:
    Value: !Ref ApiGatewayRestApi

Script to test the endpoint from browser console -

let url = "enterUrlHere";
fetch(url, {
    method : "POST"
})
.then(function(response) {
    console.log('success =>\n', response);
})
.catch(function(error) {
    console.log('error =>\n', error);
});

Error -

No 'Access-Control-Allow-Origin' header is present on the requested resource.

Reference -
Enable CORS for API Gateway in Cloudformation template

Dev1ce
  • 5,390
  • 17
  • 90
  • 150
  • One common mistake while trial/error with AWS CloudFormation + API Gateway is that you forget to re-deploy the API to the desired stage. Please ensure that you deployed everything to the stage – MaiKaY Aug 26 '19 at 12:13
  • @MaiKaY I tried again after moving the deployment resource - `ApiGatewayDeployment` at last, still same error, updated the template in the question. – Dev1ce Aug 27 '19 at 02:59
  • As long as you are not using AWS SAM (Serverless Application Model) the `AWS::ApiGateway::Deployment` resource is just created once. Which means as soon as you change something in your API you need to deploy the API manually(!) again. – MaiKaY Aug 27 '19 at 05:24
  • So despite the CF template, I need to deploy the Gateway once? Is it possible for you to copy the template in the question and correct the sequence and post as an answer? I tried various combinations but none are working for me. – Dev1ce Aug 27 '19 at 05:47

1 Answers1

3

What defines the CORS behaviour is not configured in your API GATEWAY, but in the header of your response. As a result, make sure you add them. Here is an example of my http response.

{
    statusCode: 200,
    body: JSON.stringify(resp),
    headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, DELETE',
}
Cleriston
  • 750
  • 5
  • 11