'Basic User Authentication for Static Site using AWS & S3 Bucket

I am looking to add Basic User Authentication to a Static Site I will have up on AWS so that only those with the proper username + password which I will supply to those users have access to see the site. I found s3auth and it seems to be exactly what I am looking for, however, I am wondering if I will need to somehow set the authorization for pages besides the index.html. For example, I have 3 pages- index, about and contact.html, without authentication setup for about.html what is stopping an individual for directly accessing the site via www.mywebsite.com/about.html? I am more so looking for clarification or any resources anyone can provide to explain this!

Thank you for your help!



Solution 1:[1]

Deploying Lambda@edge is quiet difficult to replicate via console. So I have created CDK Stack that you just add your own credentials and domain name and deploy.

https://github.com/apoorvmote/cdk-examples/tree/master/password-protect-s3-static-site

I have tested the following function with Node12.x

exports.handler = async (event, context, callback) => {

const request = event.Records[0].cf.request

const headers = request.headers

const user = 'my-username'

const password = 'my-password'

const authString = 'Basic ' + Buffer.from(user + ':' + password).toString('base64')

if (typeof headers.authorization === 'undefined' || headers.authorization[0].value !== authString) {

    const response = {
        status: '401',
        statusDescription: 'Unauthorized',
        body: 'Unauthorized',
        headers: {
            'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
        }
    }

    callback(null, response)
}

callback(null, request)
}

Solution 2:[2]

By now, this is also possible with CloudFront functions which I like more because it reduces the complexity even more (from what is already not too complex with Lambda). Here's my writeup on what I just did...

It's basically 3 things that need to be done:

  1. Create a CloudFront function to add Basic Auth into the request.
  2. Configure the Origin of the CloudFront distribution correctly in a few places.
  3. Activate the CloudFront function. That's it, no particular bells & whistles otherwise. Here's what I've done:

First, go to CloudFront, then click on Functions on the left, create a new function with a name of your choice (no region etc. necessary) and then add the following as the code of the function:

function handler(event) {

    var user = "myuser";
    var pass = "mypassword";

    function encodeToBase64(str) {
        var chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        for (
            // initialize result and counter
            var block, charCode, idx = 0, map = chars, output = "";
            // if the next str index does not exist:
            //   change the mapping table to "="
            //   check if d has no fractional digits
            str.charAt(idx | 0) || ((map = "="), idx % 1);
            // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
            output += map.charAt(63 & (block >> (8 - (idx % 1) * 8)))
        ) {
        charCode = str.charCodeAt((idx += 3 / 4));
        if (charCode > 0xff) {
            throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
        );
        }
        block = (block << 8) | charCode;
      }
      return output;
    }


    var requiredBasicAuth = "Basic " + encodeToBase64(`${user}:${pass}`);
    var match = false;
    if (event.request.headers.authorization) {
        if (event.request.headers.authorization.value === requiredBasicAuth) {
            match = true;
        }
    }

    if (!match) {
      return {
        statusCode: 401,
        statusDescription: "Unauthorized",
        headers: {
          "www-authenticate": { value: "Basic" },
        },
      };
    } 

    return event.request;
}

Then you can test with directly on the UI and assuming it works and assuming you have customized username and password, publish the function.

Please note that I have found individual pieces of the function above on the Internet so this is not my own code (other than piecing it together). I wish I would still find the sources so I can quote them here but I can't find them anymore. Credits to the creators though! :-)

Next, open your CloudFront distribution and do the following:

  1. Make sure your S3 bucket in the origin is configured as a REST endpoint and not a website endpoint, i.e. it must end on .s3.amazonaws.com and not have the word website in the hostname.

  2. Also in the Origin settings, under "S3 bucket access", select "Yes use OAI (bucket can restrict access to only CloudFront)". In the setting below click on "Create OAI" to create a new OAI (unless you have an existing one and know what you're doing). And select "Yes, update the bucket policy" to allow AWS to add the necessary permissions to your OAI.

  3. Finally, open your Behavior of the CloudFront distribution and scroll to the bottom. Under "Function associations", for "Viewer request" select "CloudFront Function" and select your newly created CloudFront function. Save your changes.

And that should be it. With a bit of luck a matter of a couple of minutes (realistically more, I know) and especially not additional complexity once this is all set up.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Apoorv Mote
Solution 2 hendrikbeck