37
votes

CORS is really driving me crazy and I'm really out of ideas as of what to try to make it work.

I have created a simple APIG Api with 1 resource called 'abc' and added 2 methods GET and POST both with Authorization set to NONE and API Key Required set to false, everything deployed to a stage called 'dev'.

Of course I enabled CORS on both methods and I see the 3 headers Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods added to the OPTIONS method and the Access-Control-Allow-Origin added to the POST and GET methods.

Both calls are mapped to the same lambda function that simply outputs a 'Hello from Lambda' text to the console.

Then I have created a simple html page I hosted as a static website on S3, pointed a domain to it using Route53 and started testing the API using jQuery $.ajax to make the calls.

All seems easy, straightforward and exactly as explained in the docs, except only the GET works and outputs the text to the console as expected. The POST version results in the following error:

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.com' is therefore not allowed access. The response had HTTP status code 400.

The preflight call works and returns 200 OK and all headers are there, but the POST call returns that error and a 400 Bad Request.

Please any help is really appreciated, I hope the AWS team is watching too...

Thanks guys.


EDITED - Copied from Google Chrome:

POST Raw Request Headers:

POST /dev/urls HTTP/1.1
Host: kykul1mshe.execute-api.us-east-1.amazonaws.com
Connection: keep-alive
Content-Length: 73
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
Content-Type: application/json
Referer: http://example.com/dev.html
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

POST Raw Response Headers:

HTTP/1.1 400 Bad Request
Date: Fri, 19 Aug 2016 02:14:16 GMT
Content-Type: application/json
Content-Length: 177
Connection: keep-alive
x-amzn-RequestId: a1160e45-65b2-11e6-9766-cd61e49fbcdb
X-Cache: Error from cloudfront
Via: 1.1 d64756b4df47ce24d6c62b5a8de97e87.cloudfront.net (CloudFront)
X-Amz-Cf-Id: N9mf7apicKbSM_MiZjePbEgZGIFKckWJ3lZljH8iHVKFVTcIIOQuHg==

This returns 400 Bad Request

OPTIONS Raw Request Headers:

Accept:*/*
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:kykul1mshe.execute-api.us-east-1.amazonaws.com
Origin:http://example.com
Referer:http://example.com/dev.html
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

OPTIONS Raw Response Headers:

Access-Control-Allow-Headers:Content-Type,X-Amz-Date,Authorization,X-Api-Key,Cache-Control,X-Requested-With
Access-Control-Allow-Methods:POST,OPTIONS
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:79
Content-Type:application/json
Date:Fri, 19 Aug 2016 02:14:16 GMT
Via:1.1 d64756b4df47ce24d6c62b5a8de97e87.cloudfront.net (CloudFront)
X-Amz-Cf-Id:KpGEDmIuf5RHcUnBWuA3oEMZgWHwrjy3SpLuOflRhAD8IIx5vyKGSw==
x-amzn-RequestId:a10bae11-65b2-11e6-bcf7-63b49c24629e
X-Cache:Miss from cloudfront

This returns 200 OK

9
Hi, I am from api gateway. I don't see anything wrong with the way you setup your api. Could you update with the raw requests? That'll help in debugging.Abhigna Nagaraja
Thank you for replying @AbhignaNagaraja - I updated the post with the headers I got in Google Chrome (I've just hidden the real domain name).HBR

9 Answers

26
votes

Ok, I found the origin of the problem, which happens to be totally unrelated to APIG, and confirms what @AbhignaNagaraja mentioned, that my APIG was properly configured.

The issue is actually in the way I called jQuery.ajax, which I thought was smart enough to convert my parameters to a JSON string when contentType is 'application/json'. It seems I had to manually stringify the JSON params rather than passing a JSON and having jQuery stringify it.

So this is the bad call:

$.ajax({
        url: myEndpoint,
        type: 'POST',
        crossDomain: true,
        data: {
            url: $('#url').val()
        },
        headers: {
            "X-Api-Key": 'blablabla'
        },
        dataType: 'json',
        contentType: "application/json",
        success: function (data) {
            console.info(data);
        }
    });

And this is the right call:

 $.ajax({
        url: myEndpoint,
        type: 'POST',
        crossDomain: true,
        data: JSON.stringify({
            url: $('#url').val()
        }),
        headers: {
            "X-Api-Key": 'blablabla'
        },
        dataType: 'json',
        contentType: "application/json",
        success: function (data) {
            console.info(data);
        }
    });

This can be a hint if you are debugging such an issue with CORS: just download the AWS APIG SDK and try executing the call using the apigClient provided by AWS and compare headers with the ones you get with your custom client. When examining the 2 sets of headers I got with jQuery and apigClient, I noticed the Request Payload looked different and thats how I realized the format was wrong, then the 400 code and the No 'Access-Control-Allow-Origin' header is present all made sense.

I hope this helps.

22
votes

If you are using proxy integration in API Gateway, then enabling CORS from API Gateway doesn't work. You have to set the Header 'Access-Control-Allow-Origin' from your Lambda code itself.

Its mentioned in the doc.

Python code sample:

    response = {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps({'message': 'CORS enabled')
    }
    return response
14
votes

I had a similar issue, but with lambda proxy integration:

  • CORS activated on AWS API Gateway using the browser

  • lambda-proxy integration activated

When using the lambda proxy integration, you can return custom headers from inside the code of the lambda:

        var result = {
        statusCode: data.statusCode | 200,
        headers: {
          "Access-Control-Allow-Origin": "*"
        },
        body: JSON.stringify(responseBody)
    };
    callback(null, result);

This way you get the CORS header sent. I think there might be a better way to get it to work with the lambda proxy integration without coupling the CORS inside the code of the lambda, please let me know if you know.

4
votes

I was also stuck in this error, and after digging, I found that in non 2XX responses, the API was not giving Access-Control-Allow-Origin header in the response. Hence, while the OPTION method and successful (2XX) responses had this header, the 4XX and 5XX did not. One can also confirm this using PostMan and inspecting the headers of the bad response.

After tweaking with configuration I made sure to return that header in all responses.

3
votes

I had a similar issue - and it had nothing to do with the way that the API was configured or the POST request that I was making on the front-end. What fixed the problem for me was deploying the API on AWS API Gateway. Once you create an API method/resource, and tie them to a lambda function, they do not auto deploy.

You have to click "Actions" and then "Deploy API" in order to access these MicroServices from the front-end.

2
votes

to resolve this i present to you my config of the Post configuration in apy gateway

Option method - Integration REsponse - Headers Mapping

X-Requested-With '*'
Access-Control-Allow-Headers 'Content-Type,x-requested-with,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Methods'
Access-Control-Allow-Origin 'http://localhost:4200'
Access-Control-Allow-Methods 'POST,OPTIONS'

Post Method - Integration Response - Headers Mapping

Access-Control-Allow-Origin "*" ---> can be changed by your ip obviously

I hope this helps you

2
votes

Here in 2020 and still finding other causes of the API Gateway CORS issues. For me (not using lambda proxy) I was still getting the CORS error even after using the API dropdown to enable CORS, and even after adding the 'Access-Control-Allow-Origin' header to the response from my Lambda function.

What fixed it for me as adding the 'Access-Control-Allow-Origin' to the default Gateway Responses. Within the API Gateway UI, on the left-side menu, find Gateway Responses. Select 'Default 4XX' and edit it to include the 'Access-Control-Allow-Origin'. I set the value at '*' to be totally open during testing (including the quotes). Do the same for 'Default 5XX'. My ajax calls were able get passed the CORS error and now I see the real cause - the required parameters I set for the method weren't being passed in correctly.

Yet, I don't know why did that present as a CORS error.

1
votes

There have been many posts that direct you to make sure the lambda function is returning the appropriate CORS headers, and they are correct. However, it is also critical that the json object is stringified using JSON.stringify(). It seems that Postman does this for us, so it is misleading when the Postman request and the $.ajax request send the same json object; yet one succeeds and one fails.

0
votes

Sometimes CORS error occurs if the lambda time out is small.