HTTP Basic Authentication with S3 Static Site - part 1 Basic Idea

7 minute read   Updated:

Hosting a static site with AWS S3 is quite popular. Many of our clients opted to launch their beta or marketing site using S3. We ourselves hosted many demo sites on S3 as well. One of the recurring requests was to password protect a site, especially when it’s a pre-launch, beta, or demo site that should only be available to restricted viewers.

With a regular web hosting service that runs Apache or Nginx, you can easily password protect a site using HTTP Basic Authentication which is formally defined in RFC7617. AWS S3 doesn’t support HTTP Basic Authentication nor has any equivalent feature for it.

All is not lost. Xing Quan has figured out how to password protect an S3 site using a combination of permission setting and file redirection on S3. I was wondering how he got the insights to cleverly utilize those S3 features to achieve the effect of password protection. It turned out that Xing Quan was a PM at AWS S3 according to his profile.

An alternative is s3auth, a proxy service provided by Yegor Bugayenko. It basically sits in front of your S3 bucket and implements the native HTTP Basic Auth while passing data from the S3 bucket back to end-user browsers. Kudos to Yegor Bugayenko for providing the service for free. It doesn’t cost you anything to use it. However there are legal, privacy, security, and performance concerns that make it unsuitable for medium/large sites and corporate clients.

Xing Quan’s solution is more attractive because it can be implemented purely on S3 and can also be extended to play well with other AWS services like CloudFront. Most of our clients wanted to be self-contained within AWS to avoid corporate concerns mentioned above.

Huge credit goes to Xing Quan for the technique. Still, there are some limitations that makes it not applicable for all websites. We’ll look at this in 2 parts:

Updated 5/26/17: updated for S3 new Console UI.

BASIC IDEA

The goal here is to utilize a combination of S3 functionalities to create the effect of HTTP Basic Authentication for an S3 static site.

There are 4 pieces of the Basic Idea:

  1. Restrict access to the entire site, except allow public access to Entry File and Secret File
  2. Entry File index.html that accepts an user input for the password and redirects to Secret File
  3. Secret File thisisasecret that redirects to Main File that hosts the real content of the site
  4. Main File main.html that only allows access to requests originated from the same site

We’ll be using a demo S3 bucket s3-password-demo.codeprocess.io.

1. Only allow public access to Entry File and Secret File

By default, S3 bucket is not publicly accessible. So in this case, we only need to set permission to allow public access to our chosen Entry File index.html and Secret File thisisasecret. This is done with S3 policy:

{
    "Version": "2012-10-17",
    "Id": "S3 Basic Authentication using Secret file redirection & HTTP Referer",
    "Statement": [
        {
            "Sid": "Allow public access to Entry File",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::s3-password-demo.codeprocess.io/index.html"
        },
        {
            "Sid": "Allow public access to Secret File",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::s3-password-demo.codeprocess.io/thisisasecret"
        }
    ]
}

2. Entry File index.html

The goal here is to do a JavaScript window location redirection based on the password. The index.html takes in whatever the user enters (supposedly the secret password) and using JavaScript to set the window location (URL in browser address bar) to a file named whatever

<!-- index.html -->
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Password</title>
  </head>

  <body>

    <div style="margin: auto; width:400px; padding-top: 100px; font: 16px arial, sans-serif;">
      <div style="width: 250px;">This is a restricted site.<br> Authorized access only.<br><br>
      Password:<br>
        <form name="password_form" method="post" action="javascript:location.href=window.document.password_form.page.value" style="margin:0;">
          <div style="display:inline;">
            <input type="password" name="page" autocorrect="off" autocapitalize="off" autofocus size="35">
            <input type="submit" value="Enter">
          </div>
        </form>
      </div>
    </div>

    <noscript>
      <div>Javascript is required to access this area. Yours seems to be disabled.</div>
    </noscript>

  </body>
</html>

So if the user goes to our site, say http://s3-password-demo.codeprocess.io and enters abc as the password, the index.html will redirect to http://s3-password-demo.codeprocess.io/abc

We want the user to enter the correct password, which will go to our Secret File below. However if the user enters a wrong password, the site will redirect to a non-existent file which will show an S3 error to the user. We want to handle that gracefully by configuring S3 Static Website Hosting to loopback to the index.html if such error occurs:

Note that in the formal HTTP Basic Authentication there are a username and a password. In our Entry File implementation we keep it simple and only use the password. If you wanted to, you could easily take in two inputs, a username and a password, and combine them into one string before proceeding next.

3. Secret File thisisasecret

We chose to name our Secret File as thisisasecret. The filename is the secret password that you want to use, so name it however you want your password to be. There is no need to store any content in this file. It is an empty file.

We want to set S3 Metadata Website-Redirect-Location on the Secret File to redirect it to the Main File main.html that hosts the real site content. There are 2 ways to do this:

Using S3 Console UI

Navigate to thisisasecret using AWS S3 Console UI and add a new Metadata called Website-Redirect-Location with value /main.html:

Using a command line tool

You can use AWS CLI or s3cmd to set a Metadata called x-amz-website-redirect-location with value /main.html. E.g with s3cmd you can do:

s3cmd modify s3://s3-password-demo.codeprocess.io/thisisasecret --add-header="x-amz-website-redirect-location:/main.html";

If the user enters the correct password, index.html will redirect to the correct thisisasecret file, which will then redirect to main.html below.

If the browser prompts you to download the file thisisasecret or displays the content of it, it means the redirection isn’t set properly so double check the S3 Metadata.

4. Main File main.html

This is the default file for our site. Note that besides Entry File index.html and Secret File thisisasecret that have public access, no other files including this main.html is publicly accessible.

We want to set the permission such that main.html is accessible only if it were redirected from index.html. This means if you go directly to http://s3-password-demo.codeprocess.io/main.html you won’t be able to access it, and either see an S3 error or get brought back to the index.html (if you’ve set the Error document as index.html in S3 Static Website Hosting).

We only allow access if the HTTP Request header Referer were the index.html. This is done through S3 Policy:

{
    "Sid": "Allow access only if redirected to from the same site",
    "Effect": "Allow",
    "Principal": {
        "AWS": "*"
    },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::s3-password-demo.codeprocess.io/main.html",
    "Condition": {
        "StringLike": {
            "aws:Referer": "http://s3-password-demo.codeprocess.io/index.html"
        },
        "Null": {
            "aws:Referer": "false"
        }
    }
}

INACCURATE INFO

Note that in Xing Quan’s version, he had the condition on the Referer as:

"StringLike": {
	"aws:Referer": [
		"engagement.xingdig.com/*",
		"*"
	]
}

The first string "engagement.xingdig.com/*" isn’t accurate because even though we’re using the StringLike condition and not StringEquals, somehow AWS will still match the full Referer value which needs to be a complete URL. You can change that to include the proper protocol "http://engagement.xingdig.com/*" or use the wildcard at the beginning "*engagement.xingdig.com/*".

The second string "*" is too open. It’s a wildcard that basically says any Referer will do. It means that someone can easily create a simple 1-line HTML with a link to your main.html and click on their HTML to go to the site and bypass all your password protection.

I suspect Xing Quan didn’t notice his wrong string "engagement.xingdig.com/*" because the second string "*" overrode everything and made it seem to work.

LIMITATIONS

Besides the minor corrections for the Referer condition, there are 2 limitations on the Basic Idea:

  1. Typical sites will have the Main File main.html reference other files (JS/CSS) and those files need to be accessible similarly to how main.html is accessed. Without this, your site will be missing functions or styles because JS/CSS files couldn’t be loaded.
  2. The use of index.html for the Entry File conflicts with many web frameworks that rely on the default index file to be index.html instead of main.html

… continue to Part 2 where we address the limitations and more.

Updated 5/26/17: updated for S3 new Console UI.

Tags: , ,

Published:

Leave a Comment