AWS CloudFront + Lambda#Edge "The JSON output is not parsable" - amazon-web-services

I have a Lambda function (a packaged next.js app) which I'm trying to access via CloudFront. The web app works unless I try to hit the homepage.
When I hit /search or /video/{videoId} the page loads just fine.
When I try to hit the homepage, I get the following error page:
502 ERROR
The request could not be satisfied.
The Lambda function returned invalid JSON: The JSON output is not parsable. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: {id}
Why would just the homepage be invalid JSON? Where can I see this JSON to determine what is wrong? I created a mock Cloudfront request test in the Lambda function and it just returns successfully.

The problem was due to the 1 MB size limit of CloudFront Lambda#Edge responses. I didn't realize that Next.js's serverside rendering was creating a large <script id="__NEXT_DATA__"> tag on my homepage with all the fetched info from my API duplicated multiple times over. This resulted in my app's homepage being >2 MB.
I refactored my app to only send one network request, and made sure that data is only put into the __NEXT_DATA__ tag once. The app now works.

Related

How to handle 403 and 404 errors with AWS Cloudfront containing multiple origins

I have the current configuration in AWS:
S3 Bucket set to static website for my frontend
ELB which has an EC2 instance for my backend
Cloudfront distribution with two origins, two behaviors, and two custom error pages
Origins:
S3 Bucket (static website)
dev-api.mydomain.com (ALIAS record mentioned below that points to ELB)
Behaviors:
"/api/*" routes to dev-api.mydomain.com
"*" routes to S3
Error pages (this part is the most important to my question):
403 returns 200 response with index.html (which comes from S3)
404 returns 200 response with index.html (which comes from S3)
Route53:
registered domain
dev.mydomain.com points to cloudfront distribution
dev-api.mydomain.com points to ELB
How do I distinguish between the 403 and 404 errors that come back from my S3 and backend server?
Note: I am using react router to handle all routing in the static website so every request to S3 would return a 404 from S3 unless the user specifically asked for the /index.html resource.
Scenario 1: When a user goes to dev.mydomain.com/logi (mispelled login), I would expect that to hit my S3 bucket and return with a 404 which would then fetch the index.html file in the bucket. Cloudfront would return the index.html file with a 200 OK Status and my application would handle the invalid route by displaying a 404 page.
Scenario 2: When a user goes to dev.mydomain.com/login, it would hit my S3 bucket, return with a 404 and return the index.html file. Once I land on the login page, for this examples sake, I fire off a network request to dev.mydomain.com/api/non_existant_route. I would expect that to hit my API server and return with a 404 which my frontend would then handle by displaying an error message about the API request failing.
However what is happening in Scenario 2 is that a 404 is returned from my backend server to Cloudfront, which then returns the index.html file (from S3) as the response with a 200 status code.
So, my question is - Is it possible to configure Cloudfront to return different error pages based on the origin that is sending back the error code? If not, how can I accomplish returning the correct response for the backend server?
Would it require me to re-architect my AWS solution?
Another way is to:
turn on Static Web Hosting on S3
replace the S3 bucket name in CloudFront origin with the S3 Static Website URL.
remove Error Pages from CloudFront.
The error pages will be handled by the respective origins instead of CloudFront.
I was able to solve the 403 and 404 issues by removing Custom Error responses from cloudfront.
I created a Lambda#Edge function on the default behavior (*) which routes to my S3 bucket. On Origin Request, the function modifies requests to pages like /login to instead return /index.html from the bucket.
In my case, I also have two origins and both are one page app (OPA) so both contains index.js
React App (Responsible for my main application) hosted through S3 static website. (CSR build)
Webflow App (Static website for product info and blog, etc..)
Our react app has many different behaviours based on routes (application need), so I configured Webflow as default behaviour.
First, I added custom error handling for 404 with redirect page path to /index.js and HTTP Status 200, as many app routes are not directly present in S3 Website and result into 404 (This is suggested by many tech blog and works very well in case of single origin)
But as soon as I added Webflow website origin as my Default(*) behaviour, whenever 404 occurs in React app it will get redirected to default behaviours /index.js (Our static product info website)
Finally, I removed all custom error handling and it will start working as expected. All the React routes are redirecting to React behaviours and not matching behaviour routes are going to Default(*) Webflow website.

Cloudfront 403 bypass

to give some context I am for the first time trying to participate in a BugBounty program and I found out which cloudfront URL is serving content to a website and the content being served is in json format.
Each time I try to access the information through the url of the website I get the next message:
{"error":"You need to sign in or sign up before continuing."}
If I try to access the Cloudfront url(xxxxxxx.cloudfront.net) I get:
403 ERROR
The request could not be satisfied.
Bad request. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront) and a request id
I would like to know if somebody know a good article that explains how to bypass those 403 or 401 messages and obtain the JSON output
Thank you very much for all the information provided in advance.

Why doesn't my API request send on load of page cached by CloudFront?

I have an Angular 7 app hosted on an S3 bucket that makes API requests to an Elastic Beanstalk backend. The S3 bucket is fronted by CloudFront, and I'm using Lambda to intercept requests for prerendering via Prerender.cloud. When I load my page (https://contrast.fm/calendar), the initial API request to load the page's data does not get sent.
This works perfectly fine in my local environment i.e. the initial request to my calendar endpoint gets sent. Also works fine when I load the site via the bucket URL. I've noticed that if I invalidate * in CloudFront, the request successfully sends on page load. But any subsequent refreshes of the page do not result in requests being sent.
Therefore, I believe I have a fundamental misunderstanding of how my site should be working with regard to CloudFront. Shouldn't HTTP requests made by the app on initialization be sent regardless of whether or not the page is cached?
Got this fixed! The problem was related to prerender.cloud. Prerender.cloud injects a monkey patch that caches AJAX requests.
To resolve, you can put prerendercloud.set('disableAjaxPreload', true) in handler.js if you're using prerender.cloud's CloudFront + Lambda#Edge library. However, this isn't ideal because it will cause the screen to flicker, since stale cached data will disappear and new data from AJAX requests will replace it on load.
You can also set window.__PRELOADED_STATE_PLAIN__. This disables the monkey patch entirely and is well documented here: https://www.prerender.cloud/docs/server-client-transition/state

Invoking a Lambda through API-Gateway giving 403 response?

I am using AWS codestar to deploy by react application using serverless nodejs template. This is the url that is given by codestar after successfully completion of all the stages https://xxxxx.execute-api.us-east-1.amazonaws.com/Prod . This url displayed all the components in my app correctly. In navbar of my app i have items like this a ,b,c. where clicking on each one of them will redirect to a new component.(i.e.https://xxxxx.execute-api.us-east-1.amazonaws.com/a,https://xxxxx.execute-api.us-east-1.amazonaws.com/b etc. But when i refresh the page which is having a url like this https://xxxxx.execute-api.us-east-1.amazonaws.com/b i am getting a error like {"message":"Forbidden"} and in my console it is showing like this favicon.ico:1 GET https://xxxx.execute-api.us-east-1.amazonaws.com/favicon.ico 403
It seems the chrome is fetching the favicon based on the https link, which fails because there is no such favicon at the location. I tried to remove favicon.ico link in index.html but even then the chrome is using the same url to fetch the favicon which eventually fails. I followed max number of suggestions in SO to acheive this but no luck. Is there any way to say api-gateway to exclude these favicon get requests and display my app rather than showing message forbidden.
And i am pretty sure that i had enabled logs for both the agi-gateway and lambda where i didnt find any forbidden errors(i.e.403) which is weird because i can see those 403 errors in my console.
Thanks
Any help is highly appreciated.
The https://xxxxx.execute-api.us-east-1.amazonaws.com/Prod url provided by API Gateway is the base url for your site, so those paths would have to be /Prod/a instead of /a.
One way to get around that is to register your own domain and connect it to API Gateway via a custom domain. That would allow you to have https://example.com as your base url, and your paths could stay /a, /b, etc.

Receive AccessDenied when trying to access a page via the full url on my website

For a while, I was simply storing the contents of my website in a s3 bucket and could access all pages via the full url just fine. I wanted to make my website more secure by adding an SSL so I created a CloudFront Distribution to point to my s3 bucket.
The site will load just fine, but if the user tries to refresh the page or if they try to access a page using the full url (i.e., www.example.com/home), they will receive an AccessDenied page.
I have a policy on my s3 bucket that restricts access to only the Origin Access Identity and index.html is set as my domain root object.
I am not understanding what I am missing.
To demo, feel free to visit my site.
You will notice how it redirects you to kurtking.me/home. To reproduce the error, try refreshing the page or access a page via full URL (i.e., kurtking.me/life)
Any help is much appreciated as I have been trying to wrap my head around this and search for answers for a few days now.
I have figured it out and wanted to post my solution in case anyone else runs into this issue.
The issue was due to Angular being a SPA (Single Page App) and me using an S3 bucket to store it. When you try to go to a specific page via url access, CloudFront will take (for example, /about) and go to your S3 bucket looking for that file. Because Angular is a SPA, that file doesn't technically exist in your S3 bucket. This is why I was getting the error.
What I needed to do to fix it
If you open your distribution in Cloudfront, you will see an 'Error Pages' tab. I had to add two 'Custom Error Responses' that handled 400 and 403. The details are the same for 400 and 403, so I only include a photo for 400. See below:
Basically, what is happening is you are telling Cloudfront that regardless of a 400 or 403 error, to redirect back to index.html, thus giving control to Angular to decide if it can go to the path or not. If you would like to serve the client a 400 or 403 error, you need to define these routes in Angular.
After setting up the two custom error responses, I deployed my cloudfront solutions and wallah, it worked!
I used the following article to help guide me to this solution.
The better way to solve this is to allow list bucket permission and add a 404 error custom rule for cloudfront to point to index.html. 403 errors are also returned by WAF, and will cause additional security headaches, if they are added for custom error handling to index.html. Hence, better to avoid getting 403 errors from S3 in the first place for angular apps.
If you have this problem using CDK you need to add this :
MyAmplifyApp.addCustomRule({
source: '</^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>',
target: '/index.html',
status: amplify.RedirectStatus.REWRITE
});
The accepted answer seems to work but it does not seem like a good practice. Instead check if the cloudfront origin is set to S3 Bucket(in which the static files are) or the actual s3 url endpoint. It should be the s3 url endpoint and not the s3 bucket.
The url after endpoint should be the origin
Add a Cloudfront function to rewrite the requested uri to /index.html if it doesn't match a regex.
For example, if none of your SPA routes contain a "." (dot), you could do something like this:
function handler(event) {
var request = event.request
if (/^(?!.*\..*).*$/.test(request.uri)) {
request.uri = '/index.html'
}
return request
}
This gets around any kind of side effects you would get by redirecting 403 -> index.html. For example, if you use a WAF to restrict access by IP address, if you try to navigate to the website from a "bad IP", a 403 will be thrown, but with the previously suggested 403 -> index.html redirect, you'd still see index.html. With a cloudfront function, you wont.
For those who are trying to achieve this using terraform, you only need to add this to your CloudFront Configuration:
resource "aws_cloudfront_distribution" "cf" {
...
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/index.html"
}
...
}
Please check from the console which error are you getting. In my case I was getting a 403 forbidden error, and using the settings which are shown in the screenshot worked for me