Using AWS CloudFront Functions to Implement HTTP Basic Auth in BrowsersSecure Your Static Websites with Basic Authentication through CloudFront Functions

Introduction

Static websites are becoming more popular due to their simplicity, speed, and ease of deployment. Many of these sites are hosted on AWS S3 and distributed through AWS CloudFront. However, one thing often missing from these static websites is an authentication layer. That's where HTTP Basic Auth comes in. In this tutorial, we'll walk you through how to implement HTTP Basic Auth on a static website hosted on AWS S3 and distributed via CloudFront using CloudFront Functions.

The HTTP Basic Auth mechanism provides a simple way to protect your web pages with a username and password. While it's not as secure as OAuth or other robust authentication methods, it's often sufficient for small, internal-facing apps or quick prototypes. Let's delve into how to set this up with AWS CloudFront Functions.

Understanding AWS CloudFront Functions

Before diving into the implementation, it's essential to understand what AWS CloudFront Functions are. AWS CloudFront is a content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to customers globally. CloudFront Functions allow you to run JavaScript functions at over 225 CloudFront locations closer to your end-users.

CloudFront Functions can inspect and manipulate the HTTP request and response at various stages in the request/response lifecycle. This makes them perfect for implementing something like HTTP Basic Auth, where you need to examine the HTTP headers to see if a request is authenticated. For more detailed information, you can read the official AWS documentation.

Setting Up Basic Auth

One straightforward way to set up Basic Auth is by hardcoding the username and password in your CloudFront Function. Before doing this, you'll need to generate an Authorization header that contains the Base64-encoded username and password. You can easily generate this header using online tools like DebugBear's Basic Auth Header Generator.

After generating your Authorization header, create a CloudFront Function via the AWS Console. In this function, you'll check if the Authorization header in the incoming request matches the header you generated. If it does, the request proceeds; otherwise, you can return a 401 Unauthorized error.

Creating the CloudFront Function

The Bare Necessities

The first step to implementing HTTP Basic Auth is to create a CloudFront function that will handle your authentication logic. Navigate to AWS CloudFront, then click on "Functions" on the left side of the dashboard. From there, create a new function with a name of your choice; you don't need to worry about specifying a region.

The Code

The following code adds Basic Auth into the request. Insert it as the code for your function:

function handler(event) {
    var user = 'MY_USERNAME'; // TODO: Change this to your username
    var pass = 'MY_PASSWORD'; // TODO: Change this to your password

    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;
}

A more advanced version uses SHA256 hashing and IP whitelisting for better security:

function handler(event) {
    var crypto = require('crypto');
    var headers = event.request.headers;
    var wlist_ips = ['1.1.1.1', '2.2.2.2'];
    var authString = '9c06d532edf0813659ab41d26ab8ba9ca53b985296ee4584a79f34fe9cd743a4';
    if (
        typeof headers.authorization === 'undefined' ||
        crypto.createHash('sha256').update(headers.authorization.value).digest('hex') !== authString
    ) {
        if (!wlist_ips.includes(event.viewer.ip)) {
            return {
                statusCode: 401,
                statusDescription: 'Unauthorized',
                headers: {
                    'www-authenticate': { value: 'Basic' },
                    'x-source-ip': { value: event.viewer.ip },
                },
            };
        }
    }
    return event.request;
}

Command below may be used to get correct authString hash value for username user and password password:

printf "Basic $(printf 'user:password' | base64 -w 0)" | sha256sum | awk '{print$1}'

Activating the CloudFront Function

Final Steps

After you've set up your origin and permissions, it's time to associate your function with your CloudFront distribution. Scroll down to "Function associations," and for "Viewer request," select "CloudFront Function." Choose your newly-created function from the list and save your changes.

Testing and Debugging

Once the CloudFront Function is deployed and associated with your CloudFront distribution, it's time to test. You can either use curl commands or simply access the website from your browser. If implemented correctly, your browser should prompt you to enter a username and password.

If you face any issues, make sure to check CloudFront Function logs. AWS CloudFront provides logging capabilities that can help you debug any issues with your function.

Conclusion

HTTP Basic Auth is a straightforward yet effective way to add an authentication layer to your static websites. AWS CloudFront Functions offer an efficient method for implementing this authentication mechanism without requiring a separate server. By following this tutorial, you've now added an extra layer of security to your static website.

Remember, while Basic Auth provides a layer of security, it is not recommended for highly sensitive applications. For more secure and complex authentication methods, consider integrating OAuth or JWT tokens with AWS Cognito or another identity provider. Nonetheless, for quick projects or internal-facing web apps, this method can serve you well.

Further Reading