How to handle 403 and 404 errors with AWS Cloudfront containing multiple origins - amazon-web-services

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.

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/

Can an angular app on AWS S3 served via cloudfront resolve to another route apart from the index route?

I have an angular app hosted on S3. When I try to directly go to a route like myapp.com/items I get a 403 forbidden error. But when I access the app from myapp.com everything is okay and from the app I can navigate to myapp.com/items. I don't understand why this is happening.
How can I make it so that routes like myapp.com/items can be accessed directly and not throw an error?
On CloudFront distribution enter error pages tab
Then create a new error response to deal with any othe routes and redirect it to index.html file

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.

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

302 Redirect in AWS Cloudfront

I have a webstack that is currently serving both dynamic and static content. I want start serving the static content from AWS CloudFront without having to change all of the urls.
So I set up CloudFront to serve the static content from AWS S3 and the dynamic content from a custom url (current webstack). This worked however CloudFront was making the request to our webstack via a rewrite rule.
Is it possible for CloudFront to return a 302 redirect so that the request to my webstack would come from the users browser?
Unfortunately this is critical for our application to work properly.
I don't think it's possible, because CloudFront should follow 302 redirects and cache the result, rather than return the 302 redirect to the client.