CloudFront Function for basic auth, redirect, and serving from S3

I recently had the opportunity to rethink how some CDN customizations were happening within an Amazon AWS managed project. Previously, we had used a couple Lambda@Edge functions to perform some light modifications during the Viewer request and Origin request cache behaviors. We had largely inherited the setup so hitting the reset button with some needed changes seemed a good idea.

Initial CloudFront Function Researchanchor

Enter CloudFront Functions, I had actually not heard of them previously but apparently they’ve been around since 2021. After getting up to speed reading up on the use case it seemed an ideal evolution from Lambda@Edge. The parts I was most excited about was reading the following:

offers submillisecond startup times, scales immediately to handle millions of requests per second, and is highly secure

Our use case needed the following:

  1. Basic Authentication for all lower environments, because we don’t want public access to content that’s still a work in progress
  2. More control over redirects, because we’re using a JavaScript framework we weren’t able to control the response status as well as we would have liked (301 instead of 302 for example)
  3. Add index.html to request URLs that don’t include a file name, also because we’re using a statically generated site based on a JavaScript framework.


I was initially surprised – coming from the Lambda functions mentality – that I wasn’t able to use a couple of the JavaScript niceties like the Nullish coalescing operator (??), Optional chaining operator (?.), and Conditional (ternary) operator. Found out that that’s because CloudFront functions only support ES 5.1.

I was also pleasantly surprised to see a high focus on optimization and utilization, you even get a handy “Compute Utilization” score every time you test so you can keep tabs on ensuring it’s comfortably within the max allowed time.

While much of these weren’t an issue for me, I also found out that:

  • Dynamic code evaluation (eval for example) and Timers are not supported.
  • The maximum memory assigned to CloudFront Functions is 2MB
  • CloudFront Functions only respond to Viewer triggers whereas Lambda@Edge can work with both Viewer and Origin triggers. In my case I was able to merge the code we were using in both situations without any issue, but your mileage may vary.
  • CloudFront Functions only support JavaScript (ES 5.1 as noted above), Lambda@Edge supports Node.js and Python too.
  • CloudFront Functions do not have access to the network or filesystem.
  • CloudFront Functions can only manipulate HTTP headers.


  • Overall cheaper, and there’s a free tier available too.
  • Building & testing are so much faster because you can do so directly within CloudFront, no more saving, creating a version, attributing that to the CloudFront cache behavior and deploying to test!
  • Speed and scalability are amazing, you can support 10,000,000 requests per _second_ or more compared to up to 10,000 requests per second per Region for Lambda@Edge.

If you’re interested in more notes to help you choose which way to go, this Choosing between CloudFront Functions and Lambda@Edge section was really helpful for me.

The Codeanchor

Let’s get to work!

Final source

var authRedirect = {
  statusCode: 401,
  statusDescription: "Unauthorized",
  headers: {
    "www-authenticate": {
      value: 'Basic realm="Enter credentials to access this secure site"',

 * Basic Authentication
 * On all lower environment (dev & qa) we need basic authentication so general
 * public cannot access these sites. Bypass on prod and if authentication is
 * already secured.
 * @param authorization - Incoming request authorization value
 * @param host - Incoming request host value
 * @returns boolean - Whether this request is authenticated
function authCheck(authorization, host) {
  var productionHosts = ["", "", ""];

  // The Base64-encoded Auth string `secretuser:secretpass` that should be present.
  var expected = "Basic c2VjcmV0dXNlcjpzZWNyZXRwYXNz";

  // If this is a production website, we do not want to force any authentication
  if (productionHosts.indexOf(host) > -1) {
    return true;

  // If an Authorization header is supplied and it's an exact match
  if (authorization && authorization.value === expected) {
    return true;

  //Not production, auth header is either missing or failed to match
  return false;

 * Redirect Check
 * @param host - Incoming request host value
 * @param requestURI - Incoming request URI
 * @returns object|false - Returns an object containing the redirect details or false if no redirect necessary
function redirectCheck(host, requestURI) {
  redirectPaths = {
    // '[FROM]': {'Location': '[TO]', 'status': [301|302]}},
    "/old-page-1": { Location: "/new-page-1/", status: 301 },
    "/old-page-2": { Location: "/new-page-2/", status: 301 },
    "/old-page-3": { Location: "/new-page-3/", status: 302 },

  if (Object.keys(redirectPaths).indexOf(requestURI) >= 0) {
    var redirectTo = redirectPaths[requestURI];
    return redirectTo;

  return false;

 * Redirect Response
 * @param redirectTo - object containing the redirect location and status details
 * @returns object - response object with redirect details
function redirectResponse(redirectTo) {
  return {
    statusCode: redirectTo.status,
    statusDescription: "Moved",
    headers: {
      location: { value: redirectTo.Location },

 * URL Rewrite
 * Appends the /index.html to requests that don’t include a file name or extension
 * in the URL. Useful for single page applications or statically generated websites
 * that are hosted in an Amazon S3 bucket.
 * @see
 * @param uri - Incoming request URI
 * @returns
function uriRewrite(uri) {
  // Check whether the URI is missing a file name.
  if (uri.endsWith("/")) {
    return (uri += "index.html");
  // Check whether the URI is missing a file extension.
  else if (!uri.includes(".")) {
    return (uri += "/index.html");
  } else {
    return uri;

 * Primary Handler
 * Checks for authentication, then redirect, then URL rewriting
 * @param event
 * @returns
function handler(event) {
  var authorization = event.request.headers.authorization;
  var host = &&
      : "";
  var requestURI = event.request.uri.replace(/\/$/, "");

  if (authCheck(authorization, host) === false) {
    return authRedirect;

  var redirect = redirectCheck(host, requestURI);
  if (redirect !== false) {
    return redirectResponse(redirect);

  event.request.uri = uriRewrite(requestURI);

  return event.request;

Have some thoughts or feedback about this blog post?

Get a conversation started on LinkedIn or Twitter, or send me an email.


What's this?

1 Share

A. R. Younce

2 Replies

  • A. R. Younce

    A. R. Younce on

    @stevenwoodson We did this too and my only complaint is that the deployment story is pretty awkward (but still simple) compared to Lambda. I wish they’d just made it a Lambda function in the end.
  • Steve Woodson

    Steve Woodson on

    @aryounce Agreed, not an AWS expert by any stretch but my interpretation is that there are specific limitations to keep it as fast and resource light as possible. So it has to be it's own thing separate from Lambda.