AWS Static website hosting with Cognito and S3

In my journey of learning web development on the AWS platform, one of the more arduous tasks was figuring out how to use Cognito as the authentication provider for my static website hosted on S3. If I want S3 to host my static SPA (React in my case), I assumed that the “Static website hosting” option was desirable, if not a requirement. The “Static website hosting” option is on the S3 bucket’s Properties tab, as seen below:

I assumed that leaving this option ‘disabled’ would prevent S3 from being used as my webserver.

AWS tutorials and documentation do not mention how this feature exposes the static content to unauthorized users. Additionally, the Google search results from non-AWS bloggers and tutorials recommend using the “Static website hosting” option, and describe how to use Cognito as an authentication provider for the static website. It’s possible that recent S3 data breaches occurred because of exposing the buckets with “static website hosting”.

But S3’s “Static website hosting” not only allows access thru the unencrypted http protocol, it also makes the static content available to any user in the world with a browser. In the screenshot of my S3 bucket below, note that the url has the scheme http:// and not https://:

Any user can type into a browser http://s3.amazonaws.com/yourname.yourapp/index.html or http://s3.amazonaws.com/yourname.yourapp/myscript.js and download all the files from your SPA. Personally, I would like to limit access as much as possible; the guiding principle should be to deny access to your files, even to the static files which are downloaded to (authenticated) client browsers. You may have business logic in your front end files that you do not want to risk being reverse-engineered; this may be especially important if your app is an internal company app. You may also not want to risk users entering sensitive info such as passwords into an unencrypted http request.
The public availability of “Static website hosting” as defined in S3 is crazy to me because a “static” website should not necessarily be a “public” website. “Static” means simply that the files (html, css, images) are static, and is orthogonal to the matter of who can access the static content.

How to limit access to your static content

However, there is a relatively simple solution that allows you to use Cognito to prevent unauthorized access to your “static” files on S3. The solution is basically: Cognito => CloudFront => S3. The first step is to set up CloudFront.

Configure CloudFront

The first step is to add a AWSCloudFront distribution. CloudFront sits in the middle between Cognito and S3, but you need to configure CloudFront first because Cognito and S3 both need to reference it. You can find good tutorials on how to create a CloudFront distribution on AWS, obviously, and this article is a good walkthrough as well.
I will focus on the CloudFront configuration that is unique to our requirement of hosting of your static S3 pages. To do this, the requirements are:

  1. In the Behaviors tab, click “Create Behavior” and select “Redirect HTTP to HTTPS” under Viewer Protocol Policy.
  2. Click “Yes” for “Restrict Viewer Access”.
  • After your CloudFront distribution is created, go to the “Origins and Origin Groups” tab and click the “Create Origin” button. This will give your CloudFront distribution a “Origin Access Identity” (OAI) that you will add to the ACL (Access Control List) for your S3 bucket. The 14-character unique identifier after origin-access-identity/cloudfront/ is what you will need to give to S3.
  • Point Cognito to CloudFront

    Cognito acts as the gatekeeper, preventing unauthorized users from accessing your S3 files. But instead of pointing directly to S3, it should point authenticated users to the CloudFront distribution, which is really just a localized cache for your S3 files.
    In Cognito’s User Pool for your app, the App client settings Callback URL(s) should point to the newly created CloudFront distribution. This URL for the callback:

    … should match the domain name in CloudFront

    On the App clients tab in Cognito, the “App client secret” should be blank. You should not use a client secret for authenticating a web client, at least not if you are using the “Hosted UI” option like I do.

    Additionally, on Cognito’s “App client settings” tab, you should have only “implicit” grant checked under “Allowed OAuth Flows.” Successful login by the return will redirect to the callback url, which will hand a id_token and a access_token in the querystring. You should use the id_token and not the access_token. Your SPA should include this id_token in a authorization header for requests to your api gateway, and a 200 status code in the rest api’s response indicates that the user of the web app is authenticated. You can see a POC of how the SPA should use the id token in my GitHub repo

    Configure S3 for CloudFront

    Now that the CloudFront distribution is created, the next step is to set the Bucket Policy for your S3 bucket to allow access to your CloudFront distribution and deny access to everyone else. In your S3 bucket’s Permissions tab, click the “Bucket Policy” button and paste the following

     {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "2",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity YOUR_OAI"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
            }
        ]
    }
    

    Be sure to replace YOUR_OAI and YOUR_BUCKET_NAME with your own corresponding values.

    Add Authentication to CloudFront

    At this point, users cannot request files directly from the S3 bucket. And users are redirected from the Cognito login page to the CloudFront distribution. Yet unauthenticated users can still access the files directly from CloufFront because CloudFront does not yet require authentication before serving up files from S3 or its own cache. The best walkthrough of the process of using a Lambda to authenticate CloudFront users is here.

    To summarize, Cognito points to the CloudFront distribution. Thru CloudFront, the user gets access to the static files in S3.

    One thought on “AWS Static website hosting with Cognito and S3

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s