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

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

Related

How to use CloudFront as reverse proxy to my App Runner service?

I have an App Runner service running a NodeJS/Express server which is a REST API. App Runner has given me the following end point -
https://example_server_random_id.awsapprunner.com
The frontend is developed using React and deployed in S3 as a static website. I have created a CloudFront distribution for serving the static content from S3 and have a default behavior (*). CloudFront has given me the following URL -
https://cloud_front_ranom_id.cloudfront.net
So I have updated the backend code to support CORS and added the CloudFront URL as allowed origin. From the React app, I am using the App Runner URL as my base URL for all the API calls. This setup works perfectly.
Now, I wanted to get rid of CORS related setup by using the CloudFront distribution as a reverser proxy to my App Runner service. To do that, I have done following -
I have added a custom origin that points to my App Runner service endpoint - https://example_server_random_id.awsapprunner.com
I have created a new behavior (/api*) that has precedence 0, so it should be handling all the requests that have "/api" in its path instead of the default behavior (*). This behavior uses the custom origin that I have created in the previous step.
I have allowed all the HTTP request (GET, POST, OPTION, PUT etc.) for this new behavior.
For the new behavior, I have used "CachingDisabled" as the Cache Policy and "All Viewer" as the Origin Request Policy.
I have replaced the base URL and use /api as the new base URL instead of the App Runner endpoint.
I did not remove the CORS related settings from the backend code (access-control-allow-origin header value is the CloudFront endpoint and access-control-allow-credential header value is true).
This setup is not working as expected. I can still access the static contents but for all the requests to the backend (with /api base URL) give me the following errors -
Cannot POST /api/users/sign-in Status Code: 404
Cannot GET /api/users Status Code: 404
etc.
Can you please let me know if there is any way to debug this and what could be the problem in the setup?
I am writing down how I fixed this in case someone faced the same issue.
The following article helped me to pinpoint the issue - https://advancedweb.hu/how-to-debug-cloudfront-origin-requests/
Basically, the problem was the cache policy I was using. I used the following webhook tester to find out the header values, query parameter and cookies and modified the cache policy to use the legacy cache settings (Appropriate header, cookies and query parameters)-
https://webhook.site/

AWS CloudFront + Lambda#Edge "The JSON output is not parsable"

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.

nginx API cross origin calls not working only from some browsers

TLDR: React app's API calls are returning with status code 200 but without body in response, happens only when accessing the web app from some browsers.
I have a React + Django application deployed using nginx and uwsgi on a single centOS7 VM.
The React app is served by nginx on the domain, and when users log in on the javascript app, REST API requests are made to the same nginx on a sub domain (ie: backend.mydomain.com), for things like validate token and fetch data.
This works on all recent version of Firefox, Chrome, Safari, and Edge. However, some users have complained that they could not log in from their work network. They can visit the site, so obviously the javascript application is served to them, but when they log in, all of the requests come back with status 200, except the response has an empty body. (and the log in requires few pieces of information to be sent back with the log in response to work).
For example, when I log in from where I am, I would get response with status=200, and a json object with few parameters in the body of the response.
But when one of the users showed me the same from their browser, they get Status=200 back, but the Response is empty. They are using the same version of browsers as I have. They tried both Firefox and Chrome with the same behaviours.
After finally getting hold of one of the user to send me some screenshots. I found the problem. In my browser that works with the site, the API calls to the backend had Referrer Policy set to strict-origin-when-cross-origin in the Headers. However on their browser, the same was showing up as no-referrer-when-downgrade.
I had not explicitly set the referrer policy so the browsers were using each of their default values, and it differed between different versions of browsers (https://developers.google.com/web/updates/2020/07/referrer-policy-new-chrome-default)
To fix this, I added add_header 'Referrer-Policy' 'strict-origin-when-cross-origin'; to the nginx.conf file and restarted the server. More details here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
The users who had trouble before can now access the site API resources after clearing cache in their browsers.

How to block requests from apps like postman, insomnia and allow only one origin - django rest framework

I try to block the possibility to send a post request to my app via postman or insomnia. I want to limit the source of a request to one domain www.sample.com. I did add this domain to the ALLOWED_HOSTS and CORS_ORIGIN_WHITELIST, and nothing more and still I can send a request and save the data in my django app.
How can I limit the origin of a request to the one domain and block all others or return "Unauth msg"?
Stack: DJANGO REST Framework
I am able to add a check:
if not request.headers.__contains__("Origin") or request.headers["Origin"] not in HOSTS:
But I can fill the free field with key Origin and domain as a value in the postman app
You cannot prevent a HTTP client from setting a header. If your API is available on Internet it is possible to send you any header. CORS (Cross Origin Resource Sharing) rules are only applied in Web browsers.

Is it possible to set Content-Security-Policy headers in Amazon S3?

I'm trying to set a Content-Security-Policy header for an html file I'm serving via s3/cloudfront. I'm using the web-based AWS console. Whenever I try to add the header:
it doesn't seem to respect it. What can I do to make sure this header is served?
I'm having the same problem (using S3/CloudFront) and it appears there is currently no way to set this up easily.
S3 has a whitelist of the headers permitted, and Content-Security-Policy is not on it. Whilst it is true you can use the prefixed x-amz-meta-Content-Security-Policy, this is unhelpful as there is no browser support for it.
There are two options I can see.
1) you can serve the html content from a webserver on an EC2 instance and set that up as another CloudFront origin. Not really a great solution.
2) include the CSP as a meta tag within your html document:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src http://*.foobar.com 'self'">
...
This option is not as widely supported by browsers, but it appears to work with both Webkit and Firefox, so the current Chrome, Firefox, Safari (and IOS 7 Safari) seem to support it.
I chose 2 as it was the simpler/cheaper/faster solution and I hope AWS will add the CSP header in the future.
S3/CloudFront takes any headers that the origin set and forward those to the client, but you can't set custom headers on you response directly.
You can use Lambda#Edge function that can inject security headers through CloudFront.
Here is how the process works: (reference aws blog)
Viewer navigates to website.
Before CloudFront serves content from the cache it will trigger any
Lambda function associated with the Viewer Request trigger for that
behavior.
CloudFront serves content from the cache if available, otherwise it
goes to step 4.
Only after CloudFront cache ‘Miss’, Origin Request trigger is fired
for that behavior.
S3 Origin returns content.
After content is returned from S3 but before being cached in
CloudFront, Origin Response trigger is fired.
After content is cached in CloudFront, Viewer Response trigger is
fired and is the final step before viewer receives content.
Viewer receives content.
Below is the blog from aws on how to do this step by step.
https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/
If you are testing through CloudFront, have you made sure you have invalidated the cached objects? Can you try to upload a completely new file and then try accessing it via CF and see if the header is still not there?
Update
Seems like custom metadata will not work as expected as per DOC. Any metadata other than the ones supported by S3 (the ones displayed in the dropdown) will have to be prefixed with x-amz-meta-