Search code examples
amazon-web-servicescorsfetchaws-api-gatewayaws-cdk

AWS CDK CORS error with Cloudfront + Static Website on S3 + API Gateway + Lambda + DynamoDb setup


I am using AWS CDK (v1.87.1 (build 9eeaa93)) to define my infrastructure as code. I use C# to define my CDK stack(s).

I have my data stored in DynamoDb and an API gateway backed by Lambda functions to read/write to the DynamoDb. This is my backend.

My frontend is a simple static website (HTML + JS) hosted on AWS S3 distributed through CloudFront.

My API works fine when I test it independently with curl or in the AWS console. However, when I call the API using the fetch() browser API from within my static website page, I get the following error (in the browser):

Access to fetch at 'https://xxxxxxxx.execute-api.ap-south-1.amazonaws.com/prod/Account' from origin 'https://abcdefg.cloudfront.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

My CorsOptions are defined as follows:

var defaultCorsPreflightOptions = new CorsOptions() {
    AllowOrigins = Cors.ALL_ORIGINS,
    AllowMethods = Cors.ALL_METHODS,
    AllowHeaders = new [] {"*"},
    AllowCredentials = true,
    MaxAge = Duration.Days(0)
};

My API is as follows:

var api = new RestApi(this, "my-api", new RestApiProps {
    RestApiName = "My Service",
    Description = "This is the service API"
});

My resource creation adds the CorsOption for preflight (In the above error message 'Account' would be a resource added to the root):

var resourceType = api.Root.AddResource(ent);
resourceType.AddCorsPreflight(defaultCorsPreflightOptions);

My lambda handler also has

if(method == "OPTIONS") {
  const response = {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Headers" : "Content-Type",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "OPTIONS,POST,PUT,GET,DELETE"
    }
  };
  return response;      
} else if(method === "POST") {
      // ... omitted
}

The JS client code that calls the REST API is:

const response = await fetch(`${api_url}/${entity}`,{
    method: 'POST',
    mode: 'cors',
    body: item,
    headers: {
        'Content-Type': 'application/json'
    }
});

The Behavior that is attached to the CloudFront distribution:

// The cloudfront distribution for the website
var behavior = new Behavior() {
    IsDefaultBehavior = true,
    AllowedMethods = CloudFrontAllowedMethods.ALL,
    MaxTtl = Duration.Seconds(0),
    MinTtl = Duration.Seconds(0),
    DefaultTtl = Duration.Seconds(0),
    Compress = false,
    ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
        QueryString = true,
        Headers = new [] {"Authorization", "Access-Control-Allow-Origin"}
    }
};

My CloudFront distribution is as follows:

var distribution = new CloudFrontWebDistribution(this, "StaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
    OriginConfigs = new [] {
        new SourceConfiguration() {
            S3OriginSource = new S3OriginConfig() {
                S3BucketSource = bucket
            },
            Behaviors = new [] {
                behavior
            }
        }
    }
});

My S3 Bucket deployment code is:

// The S3 bucket deployment for the website
var deployment = new BucketDeployment(this, "WebsiteDeployment", new BucketDeploymentProps(){
    Sources = new [] {Source.Asset("./website")},
    DestinationBucket = bucket,
    Distribution = distribution
});

I have tried looking into the AWS CDK documentation. I have tried adding the default CORS option at the API level also, but without any success. What am I missing? Please help.


Solution

  • I figured it out. The error was was in the Lambda handler for the POST / GET / DELETE / PUT methods. I needed to return the headers (example given below):

      return {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*",  // I was missing this
          "Content-Type": "application/json"   // and this
        },
        body: JSON.stringify({"id":`${id}`})   // and this was a string earlier
      };
    

    I had another error in the client side with the fetch() response handling. I was using response.json() whereas it should have been response.text() (since I was sending text in the response body earlier).

    I was misled by the curl response (in my testing) which was just the plain text whereas there was a JSON parse issue with handling the fetch() response.

    Key takeaway: Check your Lambda handler responses.