AWS API Gateway - CORS + POST not working

36,650

Solution 1

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

Solution 2

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.

Solution 3

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.

Solution 4

Here in 2021 still having CORS problems. My setup is React App -> API Gateway -> Lambda Application -> Dynamo DB. My API requires an apiKey.

If you reach all the way here, none of the above worked like mine. This how I debug my issue:

  • I've enabled CORS on my GET request which in turned generated an OPTIONS method. note that OPTIONS method should not require apiKey.

  • Enabled Cloudwatch Logs for API Gateway, where I saw requires API Key and API Key is not associated with a Usage Plan for API Stage

  • Added apiKey in Usage Plan.

  • Called both GET and OPTION method on Postman to test it's working. (both getting status 200 OK)

  • Noticed still getting this error

enter image description here

  • but upon further inspection headers, I allowed specific headers only when I enabled CORS on my API Gateway

enter image description here

In other words, Access-Control-Allow-Headers Needs to match the headers you add in your actual request

           {
                'Content-Type': 'application/json',
                'X-API-Key': 'secret',
                //'Access-Control-Allow-Origin': '*',
            }

For me adding: 'Access-Control-Allow-Origin': '*' from the request point of view actually was causing the CORS issue, since it was not part of the default allow headers.

Solution 5

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.

Share:
36,650

Related videos on Youtube

HBR
Author by

HBR

1- PHP Developer 2- iOS Developer 3- Mac lover

Updated on December 07, 2021

Comments

  • HBR
    HBR over 2 years

    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

    • Abhigna Nagaraja
      Abhigna Nagaraja over 7 years
      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.
    • HBR
      HBR over 7 years
      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).
  • Kevin B
    Kevin B over 6 years
    "It seems I had to manually stringify the JSON params rather than passing a JSON and having jQuery stringify it.": If it were json, jquery wouldn't have needed to stringify it. What you were passing was an object, not json, and jquery converts anything that isn't a string to a string (param string). By stringifying it, you turned it into a string and thus jquery didn't touch it.
  • deanmau5
    deanmau5 over 6 years
    Thanks, stringifying resolved the same problem for me.
  • gigi2
    gigi2 over 6 years
    so it makes sense for an invalid JSON payload to incur the absence of CORS header in APIG response?
  • openwonk
    openwonk over 5 years
    I understand this was over two years ago, however do you remember if you used lambda proxy integration or not?
  • openwonk
    openwonk over 5 years
    This actually works when using Lambda proxy integration. Thanks!
  • openwonk
    openwonk over 5 years
    Side note, I also enabled CORS on each resource in my API Gateway instance... not sure if that helped, however it did add / allow for a CORS header that (I see now) the Lambda function produces.
  • MichaelChan
    MichaelChan over 5 years
    Had the same problem and the issue was I started with "Use Lambda Proxy integration" which doesn't allow this mapping in the first place.
  • Andres Navarro
    Andres Navarro over 5 years
    hi , do you have screen shots ?? headers are some kind of hidden
  • MichaelChan
    MichaelChan over 5 years
    This only works if you have "Lambda Proxy Integration" disabled. Otherwise, the header should just be sent as part of the response from your lambda.
  • Andres Navarro
    Andres Navarro over 5 years
    exactly my friend
  • Mirek
    Mirek almost 4 years
    This is the correct answer. Worked for me for both Node and Python. thank you!
  • gman
    gman over 3 years
    Thank you. I must have spent 20 hrs fighting with this stuff. Here's a more direct link to the "it doesn't work with proxy integration" docs
  • joel
    joel over 3 years
    from one cherian to another. Thank you
  • joel
    joel over 3 years
    Thank you kind sir. This answer of yours has helped me alot
  • aspnetdeveloper
    aspnetdeveloper over 3 years
    stringifying json object when making a call to Lambda solved this issue for me. Thanks. Not sure why it gives an error about CORS in the first place though.
  • David Azar
    David Azar over 3 years
    Thank you! I can't believe this was the problem... All endpoints of my app were working except for one, and it turned out to be a timeout issue.
  • Rafsan Uddin Beg Rizan
    Rafsan Uddin Beg Rizan over 3 years
    Welcome :). Also, low memory can have this same issue.
  • Prashanth Wagle
    Prashanth Wagle about 3 years
    This is strange. Any reason why it doesn't work?