Deploy a static web application on AWS, the right way

Using CloudFront and S3 to deliver an HTTPS-only static web application, while avoiding misuse of the 403 response code

Emiliano Gabrielli
faktiva
6 min readApr 14, 2019

--

Photo by Adli Wahid on Unsplash

I stumbled for a while on tons of posts, articles and tutorials about deploying static web sites on Amazon S3.
Every resource I was able to consult uses a public S3 bucket and masks 403 errors as 404; which sounds so ugly to me and should not be necessary at all, especially when https is required too.

So, my goal was to have a static web application (for example a React application) having the following requirements all fulfilled:

  1. the application has to be only served via https, mandatory nowadays for a number of quite obvious reasons
  2. the application have to take advantage of caching mechanisms to minimize the number of requests both on client to shared-cache and shared-cache to origin side
  3. the application have to be able to handle users login, this means that nonexistent files on S3 can’t be handled with custom error pages for the 403 HTTP code

To fulfill the point 1 we have to put an SSL terminator in front of the origin S3 bucket (it could be a proxy or a CDN for example).

A common and reasonable choice to terminate SSL in front of S3 is adding an AWS CloudFront distribution … and of course there’s nothing new in this approach.

I decided to write this article to expose the way we will use CloudFront, in combination with S3, to force the use of the HTTPS protocol to access the origin. Choosing CloudFront gives us the opportunity to satisfy the other points we required as well.

Let’s go ahead and see how to do the job, step by step.

Deploy on S3

Amazon Simple Storage Service (aka S3) is a fully managed AWS service implementing an object storage service that provides high scalability, data availability, security, and performance. It can be used to store and protect any amount of data at any scale and it is designed for 99.999999999% (eleven 9’s) of durability.

So, S3 buckets are a very good choice to store static assets in a simple, affordable, cost effective way. We’ll use it to store and provide HTML, CSS and JS files, as long as any other asset the application would require.

  • create an account or sign in to the AWS Console at https://console.aws.amazon.com
  • navigate to the S3 service, click Create Bucket and pick up a globally unique name for your new bucket; then click Create
  • configure to block and remove any public access attempt to the bucket
  • don’t enable the website hosting option, your bucket don’t need it and there is no reason to have it publicly accessible!

Enforce HTTPS

As said, we want to serve our application only via HTTPS. This is a security requirement and a good SEO practice. As S3 only support HTTP protocol, and we are working on AWS, the natural choice to add HTTPS support is to implement a CloudFront distribution in front of the origin S3 bucket.

This choice allow us to take advantage of a number of other desirable features, such as:

  • automatic redirection of the requests from HTTP to HTTPS directly on the edge (on the CDN Point of Presence, which means very near to the user … globally)
  • latency minimization by means of the CDN global and highly distributed nature

Let’s create a new CF distribution and follow these steps:

  • navigate to the CloudFront service, click Create Distribution and select a WEB delivery method
  • pick the S3 bucket just created in the Origin Domain Name selection drop-down
  • flag the Restrict Bucket Access and create an Origin Access Identity or pick an existing one
  • flag the Yes, Update Bucket Policy option (we’ll modify the policy later anyway)
  • in the Default Cache Behavior select the Redirect HTTP to HTTPS option
  • pick Get, HEAD in the Allowed HTTP Methods selection
  • adjust settings to maximize caching: no request headers forwarding, no cookies forwarding, no query string forwarding and automatic compression of the response objects
  • it is important to leave flagged on the Use Origin Cache Headers to allow S3 to manage caching on a per object basis (i.e. per file). This means we can (and should) use different caching strategies for different files types
  • set the Default Root Object to your application main file (for example index.html)
  • set the distribution CNAME to the domain name you want to use for your new shining static web site (or leave blank to use the distribution name)
  • to have a valid certificate you have to generate a Custom SSL Certificate here (otherwise just select the CloudFront default one)
  • click Create Distribution

Configure application routing

A static site could be make by only real static contents, such has HTML, CSS, and JS and in this case the job is already done.

But to allow a client side web application to work we need to enable its routing. This means that a “virtual path” (the route to the application action) has to be distinguished from a real server-side file or directory.
The only one to know this difference is the application itself, not the server and not the CDN. It has to properly manage existing routes and return a not found for stuff that isn’t handled. Client side!

To achieve this we need to define a Custom Error Response and point it to the application router (i.e. the /index.html).

Here it comes the very original part of this article.

The default access policy in S3 (the one we asked CloudFront to automatically created for us) doesn’t allow directory listing. And this is the right default, because the S3-only standard solution for a static website hosting requires to enable the website hosting option on the bucket but we don’t want to allow everybody to list our bucket contents.

But, as we protected our bucket (it is not public and it is only accessible through the CloudFront distribution we just created), we can safely relax this configuration and modify the bucket policy, allowing the bucket listing:

And this is the Columbus egg! As this allows us to distinguish between the 403 and 404 S3 status codes. This is important, because it enables us to require an authorization in our application, if we need it. If we can’t distinguish between an “403: forbidden” and a “404: not found” then a failed login is mapped to a not found and then handled by the router instead of showing a login form.

Remember: to handle client-side application routing don’t create a custom error page for the 403 error code! This is not necessary and it is wrong.

You are done.

Enjoy your secure and performant new shiny https-only static web application on AWS S3 plus CloudFront :-)

--

--

Emiliano Gabrielli
faktiva

Site Reliability Engineer | AWS Cloud Solutions Architect | AWS SysOps Administrator | Developer