Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS headers only present on OPTIONS (AWS ApiGateway) #4681

Closed
waynerobinson opened this issue Jan 25, 2018 · 10 comments
Closed

CORS headers only present on OPTIONS (AWS ApiGateway) #4681

waynerobinson opened this issue Jan 25, 2018 · 10 comments

Comments

@waynerobinson
Copy link

waynerobinson commented Jan 25, 2018

This is a Bug Report

Description

The cors: true setting only configures the CORS headers on the OPTIONS endpoint, not the others.

I have put the files related to this example below however, if you POST to the endpoint created by this you get the following headers:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 1505
Content-Type: application/json
Date: Thu, 25 Jan 2018 03:34:07 GMT
Via: 1.1 b4479f202bc7050d8c458896bb43d335.cloudfront.net (CloudFront)
X-Amz-Cf-Id: DVyskXbK7-eRMbpsuUycXKOdpdKDnDxeLPSe9U6aGIIwLc_8uhP35Q==
X-Amzn-Trace-Id: sampled=0;root=1-5a69502f-ca8dede848ab60413220168f
X-Cache: Miss from cloudfront
x-amzn-RequestId: 9949c459-0180-11e8-a46e-17ba897f5ba3

Even though the OPTIONS for the same URL are correct:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: false
Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
Access-Control-Allow-Methods: OPTIONS,POST
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 0
Content-Type: application/json
Date: Thu, 25 Jan 2018 03:34:34 GMT
Via: 1.1 a407e08e5e7fd31db8f285c50521951c.cloudfront.net (CloudFront)
X-Amz-Cf-Id: O6PLZp6VxWxtNsb6fcg-_CcrJGCpVmD4bdmsxAGoMHi98TwMRvHVSQ==
X-Cache: Miss from cloudfront
x-amzn-RequestId: a93dc82f-0180-11e8-8d90-517849cc68d9

For example, the following serverless.yml file:

service: missingCorsOnPostDemo

provider:
  name: aws
  runtime: nodejs6.10

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: post
          cors: true

Produces the following cloudformation-template-update-stack.json file:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ServerlessDeploymentBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "AccelerateConfiguration": {
          "AccelerationStatus": "Suspended"
        }
      }
    },
    "HelloLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/missingCorsOnPostDemo-dev-hello"
      }
    },
    "IamRoleLambdaExecution": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": {
              "Fn::Join": [
                "-",
                [
                  "dev",
                  "missingCorsOnPostDemo",
                  "lambda"
                ]
              ]
            },
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/missingCorsOnPostDemo-dev-hello:*"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:PutLogEvents"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/missingCorsOnPostDemo-dev-hello:*:*"
                    }
                  ]
                }
              ]
            }
          }
        ],
        "Path": "/",
        "RoleName": {
          "Fn::Join": [
            "-",
            [
              "missingCorsOnPostDemo",
              "dev",
              "us-east-1",
              "lambdaRole"
            ]
          ]
        }
      }
    },
    "HelloLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/missingCorsOnPostDemo/dev/1516851065048-2018-01-25T03:31:05.048Z/missingCorsOnPostDemo.zip"
        },
        "FunctionName": "missingCorsOnPostDemo-dev-hello",
        "Handler": "handler.hello",
        "MemorySize": 1024,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        },
        "Runtime": "nodejs6.10",
        "Timeout": 6
      },
      "DependsOn": [
        "HelloLogGroup",
        "IamRoleLambdaExecution"
      ]
    },
    "HelloLambdaVersion3BCybMALD0HCVtee33IWNIRMV9kIL5MIyuorH4hEc10": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "HelloLambdaFunction"
        },
        "CodeSha256": "PSzzisjnTvvYknuXw+QOlAvdkQZ67qXYSvgoAi9T8W0="
      }
    },
    "ApiGatewayRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "dev-missingCorsOnPostDemo",
        "EndpointConfiguration": {
          "Types": [
            "EDGE"
          ]
        }
      }
    },
    "ApiGatewayResourceHello": {
      "Type": "AWS::ApiGateway::Resource",
      "Properties": {
        "ParentId": {
          "Fn::GetAtt": [
            "ApiGatewayRestApi",
            "RootResourceId"
          ]
        },
        "PathPart": "hello",
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodHelloOptions": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "AuthorizationType": "NONE",
        "HttpMethod": "OPTIONS",
        "MethodResponses": [
          {
            "StatusCode": "200",
            "ResponseParameters": {
              "method.response.header.Access-Control-Allow-Origin": true,
              "method.response.header.Access-Control-Allow-Headers": true,
              "method.response.header.Access-Control-Allow-Methods": true,
              "method.response.header.Access-Control-Allow-Credentials": true
            },
            "ResponseModels": {}
          }
        ],
        "RequestParameters": {},
        "Integration": {
          "Type": "MOCK",
          "RequestTemplates": {
            "application/json": "{statusCode:200}"
          },
          "IntegrationResponses": [
            {
              "StatusCode": "200",
              "ResponseParameters": {
                "method.response.header.Access-Control-Allow-Origin": "'*'",
                "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-Methods": "'OPTIONS,POST'",
                "method.response.header.Access-Control-Allow-Credentials": "'false'"
              },
              "ResponseTemplates": {
                "application/json": ""
              }
            }
          ]
        },
        "ResourceId": {
          "Ref": "ApiGatewayResourceHello"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        }
      }
    },
    "ApiGatewayMethodHelloPost": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "HttpMethod": "POST",
        "RequestParameters": {},
        "ResourceId": {
          "Ref": "ApiGatewayResourceHello"
        },
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "ApiKeyRequired": false,
        "AuthorizationType": "NONE",
        "Integration": {
          "IntegrationHttpMethod": "POST",
          "Type": "AWS_PROXY",
          "Uri": {
            "Fn::Join": [
              "",
              [
                "arn:aws:apigateway:",
                {
                  "Ref": "AWS::Region"
                },
                ":lambda:path/2015-03-31/functions/",
                {
                  "Fn::GetAtt": [
                    "HelloLambdaFunction",
                    "Arn"
                  ]
                },
                "/invocations"
              ]
            ]
          }
        },
        "MethodResponses": []
      }
    },
    "ApiGatewayDeployment1516851065057": {
      "Type": "AWS::ApiGateway::Deployment",
      "Properties": {
        "RestApiId": {
          "Ref": "ApiGatewayRestApi"
        },
        "StageName": "dev"
      },
      "DependsOn": [
        "ApiGatewayMethodHelloPost"
      ]
    },
    "HelloLambdaPermissionApiGateway": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "FunctionName": {
          "Fn::GetAtt": [
            "HelloLambdaFunction",
            "Arn"
          ]
        },
        "Action": "lambda:InvokeFunction",
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Join": [
            "",
            [
              "arn:aws:execute-api:",
              {
                "Ref": "AWS::Region"
              },
              ":",
              {
                "Ref": "AWS::AccountId"
              },
              ":",
              {
                "Ref": "ApiGatewayRestApi"
              },
              "/*/*"
            ]
          ]
        }
      }
    }
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      }
    },
    "HelloLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "HelloLambdaVersion3BCybMALD0HCVtee33IWNIRMV9kIL5MIyuorH4hEc10"
      }
    },
    "ServiceEndpoint": {
      "Description": "URL of the service endpoint",
      "Value": {
        "Fn::Join": [
          "",
          [
            "https://",
            {
              "Ref": "ApiGatewayRestApi"
            },
            ".execute-api.us-east-1.amazonaws.com/dev"
          ]
        ]
      }
    }
  }
}
@gbleu
Copy link

gbleu commented Feb 6, 2018

From the doc :

Configuring the cors property sets Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods,Access-Control-Allow-Credentials headers in the CORS preflight response.
If you want to use CORS with the lambda-proxy integration, remember to include the Access-Control-Allow-* headers in your headers object

@waynerobinson
Copy link
Author

Thanks, I did eventually find that. Although I don't understand why this isn't included by the CORS config?

@dashmug
Copy link
Contributor

dashmug commented Feb 7, 2018

I don't understand why this isn't included by the CORS config

Because if you're using lambda-proxy integration, you are bypassing request/response mapping templates and your function is solely responsible for providing the correct response.

@bsdkurt
Copy link
Contributor

bsdkurt commented Mar 24, 2018

Please close this report as it is not a bug with serverless and is document well:
https://serverless.com/framework/docs/providers/aws/events/apigateway/#enabling-cors

@samsammurphy
Copy link

Although maybe could become a feature request?

@HyperBrain
Copy link
Member

@samsammurphy The problem here is, that if you're using lambda-proxy integration, you have to handle all calls to the endpoint and its responses by yourself, i.e. you have to add the headers in your code when you assemble the lambda proxy response. The framework cannot do that for you (in its current design) because it does not implement your handler.
Having Serverless inject the headers in the response would mean to have the framework wrap your handlers or configure APIG to transform the result with a response template, which is imo not possible with lambda proxy integration (anyone here, please correct me if I'm wrong).

@samsammurphy
Copy link

@HyperBrain I see, thank you for this explanation.

@ch3ck
Copy link

ch3ck commented Oct 8, 2018

@samsammurphy The problem here is, that if you're using lambda-proxy integration, you have to handle all calls to the endpoint and its responses by yourself, i.e. you have to add the headers in your code when you assemble the lambda proxy response. The framework cannot do that for you (in its current design) because it does not implement your handler.
Having Serverless inject the headers in the response would mean to have the framework wrap your handlers or configure APIG to transform the result with a response template, which is imo not possible with lambda proxy integration (anyone here, please correct me if I'm wrong).

Can you provide a simple example for this? I just deployed an API to API Gateway i'm using proxy integration. I wish to know how to provide the proper headers to the response within the lambda function

@casper-gh
Copy link

I'm having the same issue, CORS can be enabled via AWS Console but Serverless has no option for it? Can I just add a custom Method Response with 200 status code for Access-Control-Allow-Origin header?

@dschep
Copy link
Contributor

dschep commented Feb 8, 2019

@casper-gh yes. That's the recommended way of doing this. The cleanest way to do it is also to user variables in serverless.yml to configure the cors origin only once:

custom:
  corsOrigin: '*' # change this to your desired origin!
provider:
  environment:
    CORS_ORIGIN: ${self:custom.corsOrigin}
function:
  hello:
    handler: hello.handler
    events:
      - http:
          path: /
          method: get
          cors:
            origin: ${self:custom.corsOrigin}

then a handler in NodeJs example:

module.exports.handler = async () => ({
  statusCode: 200,
  body: JSON.stringify({message: 'hello'}),
  headers: {'Access-Control-Allow-Origin': process.env.CORS_ORIGIN},
});

Here's a good blog post from @alexdebrie on using CORS w/ serverless: https://serverless.com/blog/cors-api-gateway-survival-guide/

I'm going to close this issue now tho as setting the header on the actual lambda response is out of scope for the framework.

@dschep dschep closed this as completed Feb 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants