Problem
I am deploying a SPA app ( TypeScript + React ) to AWS S3 and setting up CloudFront distributions to enable the website. It works well but has some problems.
- home page ( https://mydomain.com )
The home page works perfectly, allowing smooth navigation across all subpages, including the messages page
- messages page ( https://mydomain.com/messages) and others
When the address of the messages pages is manually input into the web browser, it will display a 403 error message, in CloudFront’s format.
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>DW2M2T3GFMBJ2RE2</RequestId><HostId>2YPthx57geaxvEGfdz63qx3zKVTbBdogpf2kIPIY/RiFn5FHDcURTp4APAe/zM+LbmE1YLS6KA4=</HostId></Error>
Reason
The HTTP 403 Forbidden client error status response code indicates that the server understood the request but refuses to authorize it.
This definition of the 403 error is quite misleading in this case. Because of it, I immediately started researching on what ‘refuses to authorize it’, and later I proved that it was not relevant to authorization after exploring all the potential authorization and distribution issues.
A great article helped me to explore all the potential issues, especially the section related to SPA.
SPA
If the website is an SPA, then we need to make sure all requests to the server (S3 in this case) return something even if no file exists. This is because SPAs like React (with React Router) need the index.html page for every requests, then things like "not found" pages are handled in the front-end.
https://gist.github.com/bradwestfall/b5b0e450015dbc9b4e56e5f398df48ff
Basically, what it says is there is no object called “/messages” in S3, and React needs index.html to response to the GET requests for all those ‘not existing’ objects.
Resolution
The resolution to this issue is create custom error responses for both error code 403 and 404, and make /index.html the response page path.