2
votes

Summary:

I'll get into the weeds below, but essentially the end goal I'm aiming for is to retrieve a value from a AWS Lambda function and return it to the client side of my page. The page is using AWS Cognito to authenticate a user and once authenticated it posts to an API Gateway endpoint to retrieve data back from my Lambda function (The Lambda queries DynamoDB). This is the part I'm struggling with because I get a 504 (Timeout) error back.

I know my Lambda returns values correctly and that my API, theoretically, is implemented correctly, or at least that it works from the test section of the API Gateway Management Console.

Details:

I've spent quite a bit of time trying to figure out exactly where my error lies, and I believe it has something to do with the Cognito authentication or CORS. If I remove the authorization requirement from my API gateway method and don't send the authorization token, then I get the expected response back from the post. This leads me to believe that maybe the root of my issue is incorrectly implementing Cognito authorization on the Post method.

I'm using CORS because the site is hosted on an EC2, but is using AWS API Gateway for its RESTful routes.

Also, I use the Cognito javascript SDK to authenticate the user, then pass the authToken into the custom headers in my ajax.

Here's my ajax:

$.ajax({
    method: 'POST',
    url: _config.api.invokeUrl + '/getusersites',
    dataType: "json",
    crossDomain: true,
    cors: true,            
    xhrFields: {
        withCredentials: true
    },
    headers: {
        Authorization: token
    },            
    data: JSON.stringify(user),
    success: completeRequest,            
    error: ajaxError  
});

Now unless I'm misunderstanding, when I look at the headers in the Network tab of Chrome's DevTools, it appears that the post is failing on the preflight. Because when I look at that General section of that URL, it says:

Request Method: OPTIONS

Status Code: 504

(See update below for full headers)

And the body is:

{"message": "Endpoint request timed out"}

So that would lead me to believe that the Option request for CORS is timing out prior to the Post even being called.

I'm also getting the console error:

Failed to load (My API endpoint here): Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '(My origin domain)' is therefore not allowed access. The response had HTTP status code 504.

So to me that looks like the CORS preflight is failing to respond before the API times out.

I do not have Authorization enabled in the method execution for the Options method on my API resource, but I do have it enabled for the Post method. As I said previously, if I disable that then everything works as expected, only there's no Cognito authorization check.

So what am I doing wrong here? Is it a CORS issue? Auth issue? Both?

What I've tried:

I attempted to add the CORS headers to the Integration Response of my Post method. However, I believe this didn't make a difference because they would only be returned with a 200 (Success) code, and I'm getting the 504 code.

I thought perhaps there was a permissions issue with either my Lambda function or in my Cognito User Pool's authorizer. So I went into IAM and gave them both all the permissions they could possibly want. This didn't change the result.

I was working under the theory my ajax might be incorrectly set up, so I've tried a few different ways of implementing the parameters of the post, however the only one that works is if I disable authorization and don't send the custom header with the auth token.

I ensured that my API resources have CORS enabled and have been redeployed. If only it had been that simple.

I've googled enough to overheat a few google servers, but I'm out of ways to ask this question.

Final Thoughts:

If you've taken the time to read my novel, I appreciate it. If anyone has tried to do something similar or has any ideas of directions I could go next with this issue. I'm still learning how to handle all this AWS implementation, so I'm sure I've just made some silly mistake somewhere.

Thank you in advance for your replies.

>>>UPDATE:<<<

Per request, here are the full headers for the OPTION attempt. There is no POST because of the 504 error on the OPTION attempt.

General:

Request URL: https://(API endpoint).amazonaws.com/prod/getusersites

Request Method: OPTIONS

Status Code: 504

Remote Address: 52.84.11.37:443

Referrer Policy: no-referrer-when-downgrade

Response Headers:

content-length: 41

content-type: application/json

date: Wed, 04 Apr 2018 16:16:14 GMT

status: 504

via: 1.1 0a9f4502819b08c3a7919c963887be2b.cloudfront.net (CloudFront)

x-amz-apigw-id: E0wHvE3doAMFgrA=

x-amz-cf-id: pVgd6gNNtw-cYxKe4s9jdEnfU2rBle8cd9MyP34aduYi0_ds4YBRCA==

x-amzn-requestid: 6e014d82-3823-11e8-bc41-3f13ca287d3c

x-cache: Error from cloudfront

Request Header:

:authority: (API Endpoint)

:method: OPTIONS

:path: /prod/getusersites

:scheme: https

accept: /

accept-encoding: gzip, deflate, br

accept-language: en-US,en;q=0.9

access-control-request-headers: authorization

access-control-request-method: POST

cache-control: no-cache

origin: https://(Origin Domain)

pragma: no-cache

user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36

1
OK, stupid question first - have you ensured that your web server allows OPTIONS requests at all? A 504 response to the CORS preflight sounds to me like it's simply that your web server (Apache or whatever) is set up to only allow GET, POST and probably HEAD... If you can supply a full set of request/response headers, that would be useful (for both the OPTIONS and POST requests.roryhewitt
I'm certain that the web server is set up correctly because when I remove the authentication requirement, the expected value is returned. I'll update my question with the full headers.Ryan Gibbs

1 Answers

8
votes

It's taken forever, but I finally figured out what was wrong.

The problem was how AWS set up the OPTIONS Integration Request when I implemented CORS through the AWS API Gateway Console.

By default, it sets up the Integration Request as HTTP, using the API invocation URL as the endpoint. For some reason, this was causing the OPTIONS preflight to time out and respond with 504. Still don't know why, likely had to do with the authentication.

Solution:

In the Integration Request section of my OPTIONS method I changed the Integration type from HTTP to MOCK.

Then, after saving that, I had to go to the Integration Response section of my OPTIONS method, expand the 200 HTTP status code, expand the Headers Mappings and reenter the values for my headers. The headers were still there, but the values had been removed. My final response headers looked like this:

Access-Control-Allow-Headers 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'

Access-Control-Allow-Origin 'https://MyDomain.com'

Access-Control-Allow-Credentials 'true'

Access-Control-Allow-Methods 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'

After setting those, I redeployed the API and It worked perfectly! Now I'm off to drink a bottle off champagne. :)