CORS error despite 'Access-Control-Allow-Origin' specified in Lambda response when using Lambda, API Gateway and Cloudfront - amazon-web-services

I have a deployed web app, it's built with a React & Redux frontend hosted on S3, and a several backend micro-services hosted on AWS Lambda and exposed over API Gateway that were deployed with Serverless. The site is also distributed via a CloudFront CDN.
The micro-services interact with several external services but the primary one I am concerned with is the get-products service which queries the Stripe product database and returns the products to my React app from there.
The site has been working fine until yesterday when I deployed some new (basically cosmetic) changes to front end, and added some more SKUs to the Stripe database. Since adding these changes I have started to experience CORS errors where previously there were none.
Initially I got the following error:
Access to XMLHttpRequest at 'https://XXXXXXXXXX.execute-api.eu-west-1.amazonaws.com/dev/products' from origin 'https://www.superfunwebsite.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This was despite my Lambda response including the following headers:
'Access-Control-Allow-Origin': '*',
At this point I updated my Lambdas response headers to the following:
'Access-Control-Allow-Origin': 'https://www.superfunwebsite.com/',
'Access-Control-Allow-Credentials': true
The error has largely persisted, except for on one random page load where instead I got an error stating that the Origin https://www.superfunwebsite.com and 'Access-Control-Allow-Origin' https://www.superfunwebsite.com/ didn't match.
I've since changed the Lambda response headers to this:
'Access-Control-Allow-Origin': 'https://www.superfunwebsite.com',
'Access-Control-Allow-Credentials': true
Which seems as though it may have solved that anomalous second error.
However, error one persists even when I am testing using a HTTP client like Insomnia. I get the following in the response.
// status code
403
// response
{
"message": "Forbidden"
}
// headers
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Tue, 18 Sep 2018 13:22:41 GMT
x-amzn-RequestId: eb691541-bb45-11e8-82ff-6d1b542dffb9
x-amzn-ErrorType: ForbiddenException
x-amz-apigw-id: NaxVLGJgjoEF5Fg=
X-Cache: Error from cloudfront
Via: 1.1 08037e15a3c6f503f39825efeb7f0210.cloudfront.net (CloudFront)
X-Amz-Cf-Id: cbNtb4xKWc48VPFon-Cl9y27KmXRVLIN5SWuYwNWlWsTXeaAXx3z-Q==
Based on the above Insomnia output it seems that my issue is somehow related to CloudFront although I don't understand exactly how. This other S/O post seems to indicate that there should be options for me in the CloudFront behaviours section that would allow me to whitelist headers for the response, however these options are not visible to me in the console, I've seen suggestions this is because the origin is on S3.
My question is simply if anyone knows how I can fix this issue or if there is something else I've not considered that I should be looking at?

Here you have to Whitelist the respective CORS headers in the behaviour section of the cloudfront distribution.
Open your distribution from the Amazon CloudFront console
Choose the Behaviors view.
Choose Create Behavior, or choose an existing behavior and then choose Edit.
For option Cache Based on Selected Request Headers, choose Whitelist.
Under Whitelist Headers, choose headers (Access-Control-Request-Headers and Access-Control-request-methods, Origin) from the menu on the left, and then choose Add.
Choose Yes, Edit.
The above settings should work for you (as its mainly for GET and HEAD) but if it doesn't, enable OPTIONS method as well using below article.
Please check the below article from aws:
no-access-control-allow-origin-error
Custom CORS Cloudfront
enhanced-cloudfront-customization

This issue has now been solved. In the end it was not in fact a CORS issue but a staging one. My query was failing because I was querying an endpoint stage that did not exist.
It appears that in the set up I had using CloudFront and API Gateway, when querying an endpoint that didn't exist I was returned a 403 response without CORS headers.
The browser/CloudFront (not sure which) then identified the absence of CORS headers and threw an error in response to that.
In my case simply changing my endpoint from the dev stage (which did not exist on the associated AWS account associated with the live stage):
https://XXXXXXXXXX.execute-api.eu-west-1.amazonaws.com/dev/products
To the live stage:
https://XXXXXXXXXX.execute-api.eu-west-1.amazonaws.com/live/products
Resolved the issue immediately.

Related

API Gateway configuration returns 403

I have an API Gateway configured and deployed. If I make a GET request to one of its staged endpoints, for example https://1234567890.execute-api.us-east-1.amazonaws.com/dev/doc, I get a 200 OK response.
If I take a look at the Custom Domain Names section and supplant the URL found there into my request, for example abcdefghijkl-f4cwy0d1u5.execute-api.us-east-1.amazonaws.com to make https://abcdefghijkl-f4cwy0d1u5.execute-api.us-east-1.amazonaws.com/dev/doc, I get 403 Forbidden.
Am I wrong in thinking that I should be able to make a request to the domain name - and thus use the API's Custom domain name in a CNAME record - or does the 403 indicate that a specific configuration item is missing?
you can find some response headers that come together with your 403 error here: https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-troubleshoot-403-forbidden/
this might help you to find which error you are facing!
TL;DR: When getting 403 Forbidden with API Gateway and using the Custom domain name it's important to trim the stage name because API Gateway is routing the custom name to that stage.
Using the documentation provided by #leoandreotti I was able to identify the response header:
x-amzn-ErrorType: ForbiddenException
For this, the documentation states:
Invoking a REST API that has a custom domain name using the default
execute-api endpoint - The caller uses the default execute-api
endpoint to invoke a REST API after disabling the default endpoint.
This made me think back to a header I had been recommended to use by a colleague - the Host header.
So, I added the header back into the request and got this:
x-amzn-ErrorType: MissingAuthenticationTokenException
For which the docs state:
Resource path doesn't exist - A request with no "Authorization" header
is sent to an API resource path that doesn't exist.
But the path /dev/doc absolutely does exist. Then I realised that the /dev portion is actually the stage name.
So I trimmed the /dev portion from the path and got 200 OK - then I removed the Host header and also got 200 OK!
Thanks #leoandreotti

Cannot query AWS API Gateway using API Key and CORs

I'm almost complete with a new web app, however, I'm getting a 403 error from AWS's API Gateway due to a CORs issue.
I'm creating a Vue app and using Vue's axios library. CORs is enabled and the request works with API Key Required option turned off in AWS's API Gateway by sending the following:
axios
.get('My-URL-Endpoint',{headers: {})
.then(response => (this.passports = response.data ));
When I turn on API Key Required functionality inside AWS's API Gateway. It works when I use Postman along with including x-api-key: My-API-Key. However, using Vue's axios it does not work and returns error 403:
axios
.get('My-URL-Endpoint', {headers: {
'x-api-key': 'My-API-Key'
}})
.then(response => (this.passports = response.data ));
My first instinct is that the problem is related to how Axios is sending the request through the browser. From what I can gather it looks like the pre-flight check is failing because I am receiving the following error within the browser:
Access to XMLHttpRequest at 'My-URL' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Sure enough it looks like there is no access-control-allow-origin key in the response. So I added access-control-allow-origin to the response of the 403 message and got a new error "It does not have HTTP ok status"
I've been trying nearly everything to get this to work! I came across stackoverflow answer where it seems like the person was suggesting that API Key Required Key functionality can't work with CORs. This kind of seemed like that cannot be true. It would be a pretty crippling restriction.
So my question is how to get the browser's pre-flight check to work along with CORs and API Key capability inside AWS's API Gateway?
Thank you!
If you have enabled cors on your api gateway, the next place to look is the application code such as lambda. Make sure the Lambda is returning the correct cross origin headers in both successful and failure scenarios.
First of all you can check if the request is reaching the lambda from the cloud watch logs. Another way to check this is to temporarily point the Api gateway target to the Mock end point.
If the mock endpoint works, then the problem is the application code. otherwise the problem is in your api gateway end point.
Also note that any headers you use should be white listed in the Access-Control-Allow-headers section when you enable to cors for your method/resource.

API Gateway CORS HTTP 415

Okay, I've been all over these interwebs looking for some insight to my issue; I've probably been through over 80 stack overflow threads RE api gateway and such, but none of them seem to help or speak close enough to my issue.
I'm new to API Gateway and cors, but lets see if i can articulate the issue that i am seeing:
Setting up a API gateway proxy to Kinesis firehose hydrating a redshift database. The proxy, firehose, and redshift gateway are up and working when called in isolation, but when called from one of our customer sites, we get an error as follows:
XMLHttpRequest cannot load [api_call_here]. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin [origin_website_here] is therefore not allowed access. The response had HTTP status code 403.
Okay, so that strongly implies that CORS is needed, right? in the console on the resource, enable cors, deploy, new error:
XMLHttpRequest cannot load [api_call_here]. Request header field $cookies is not allowed by Access-Control-Allow-Headers in preflight response.
Ooooooooookay, from the new OPTIONS method added by the enable CORS feature, in integration response, allowed headers, under access control allowed headers add '$Cookies', deploy.
Now i get a new error, very similar to the first error:
XMLHttpRequest cannot load [api_call_here]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin [origin_website_here] is therefore not allowed access. The response had HTTP status code 415.
Notice the first error had HTTP status code 403, and this third one has status code 415. This is where I'm having issues. If I go into the GET method acting as the proxy method, body mapping templates, I have "When there are no templates defined (recommended)" selected.
Now, I read that when API gateway fails to find a matching template it rejects with a 415 error, so I changed aforementioned option to "When no template matches the request Content-Type header". That made the error disappear, but the data is still not being persisted to redshift when called from the origin. Again, when I call the api directly from postman, insomnia or just a plain old address bar, the records are added nicely.
Opening up chrome and looking at the header i see that the cookie is coming across as text/html.
Regarding the template mapping up there, i have only defined a map for application/json; could that be part of the problem?
Also, the response header as viewed from chrome's console is as follows:
content-length:37
content-type:application/json
date:Wed, 19 Apr 2017 23:43:35 GMT
status:415
via:1.1 [blahblabbleblah].cloudfront.net (CloudFront)
x-amz-cf-id:[blahblabbleblah]
x-amzn-requestid:[blahblabbleblah]
x-cache:Error from cloudfront
I'm relatively new to this so i dont see how cloudfront fits in with this, especially give that it is complaining about media type while the console is complaining about no access-control-allow-origin header.
At any rate, any help as to how to resolve the third error would be most appreciated.
What is the content-type in your requests from the browser? If content-type header isn't specified in the request, then API Gateway assumes "application/json" by default.
Opening up chrome and looking at the header i see that the cookie is coming across as text/html.
I am not sure if you meant that "Content-type" header's value in the request is set to "text/html". If yes, that's the problem. You will either need a matching template, or you will need to pass through by default by choosing "When no template matches the request Content-Type header".
I have some issue with AWS API GW with SQS integration. Problem was that incorrect content type in mapping template, I wrote application/json but correct is application/x-www-form-urlencoded
The request body will never be passed through to the integration.
Requests with a Content-Type header that don't match any templates
will be rejected with a HTTP 415 response.

What Headers need to be whitelisted in AWS CloudFront for Parse Server

I'm running parse server behind AWS CloudFront and I'm still trying to figure out what the best configuration would be. Currently I've configured the CloudFront behavior to:
Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
Cached HTTP Methods: GET, HEAD (Cached by default)
Forward Headers: Whitelist
Accept-Language
Content-Type
Host
Origin
Referer
Object Caching: Customize:
Minimum TTL: 0
Maximum TTL: 31536000
Default TTL: 28800
Forward Cookies: All
My GET requests (using the parse REST API) seem to be cached as expected with this configuration. All requests that are made using the parse JS SDK seem to be called via POST and produce a 504 error in the browser console:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
For some reasons those requests are still fullfilled by the parse server because e.g. saving Objects still stores them into my MongoDB even though there's this Access Control Origin error.
The fix for this is not through cloud front but it will be from the Parse Server side.
In this file /src/middlewares.js add the below code and the cloud from will not thorough that exception.
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type');

AWS CloudFront CORS Support

I am trying to build an app where users upload content on their browsers to an S3 bucket through CloudFront. I have enabled CORS on the S3 bucket and ensured that the AllowedOrigin is set to *. I can successfully push content from a browser to the S3 bucket directly so I know that CORS on S3 is configured correctly. Now, I am trying to do the same with browser -> CloudFront -> S3. CloudFront always rejects the pre-flight OPTIONS method request with a 403 forbidden response.
I have the following options enabled on CloudFront:
Allowed HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
Whitelist Headers: Access-Control-Request-Headers,
Access-Control-Request-Method, Origin OPTIONS requests are disabled
from the "Cached HTTP Methods"
CloudFront apparently now supports CORS but has anyone got it working for an HTTP method OPTIONS request? I tried asking this on the AWS forums but no responses.
Have your try adding a CNAME alias for your cloudfront domain ??
After setting up the CNAME alias, you can set the cookies on the base domain, then you will be able to pass your cookie.
Let's put more detail to it in case people want to know what would be the next step is, let's use the following example :-
You are developing on my.fancy.site.mydomain.com
Your Cloudfront CNAME alias is content.mydomain.com
Make sure you set your cloudfront signed cookies to .mydomain.com from your fancy app
From this point on, you are able to pass the cookie for the CF.
One quick way to test if your cookie is set appropriately, try to get your assets URL, and put the url in the browser directly. If the cookie set correctly, you will be able to access the file directly.
If you are using javascript to get the cdn assets, make sure in your JS code, you need to pass withCredentials option, or it won't work. For example, if you are using jQuery, you will need something like the following :-
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
And if the request is successful, you should get a response header from CloudFront with "Access-Control-blah-blah".
Hope it helps people if they search this answer.
I found a very similar issue. The CloudFront distribution was not sending the header information to S3. You can test this easily via:
curl -i -H "Origin: http://YOUR-SITE-URL" http://S3-or-CLOUDFRONT-URL | grep Access
If you have the same problem, you can see my solution here:
AWS S3 + CloudFront gives CORS errors when serving images from browser cache