Securing Static Websites with Basic HTTP Authentication and CloudFront
How to password protect your static website with basic HTTP authentication (RFC 7617)
Introduction
Have you ever needed to quickly add authentication to protect content or a static website? Basic HTTP Authentication can be a simple and effective way to secure your content. It doesn’t require a complex configuration, and works well in many scenarios. In this article, we’ll explore how to set up Basic HTTP Authentication for a website served through Amazon Simple Storage Service (S3) and Amazon CloudFront.
- Have the AWS CLI Configured 2. Have the AWS CDK Configured and bootstrapped in the region of your choice. 3. Have a static website hosted using Cloudfront and S3. - If you don't know how to do that yet. Feel free to take a look at my other posts—SPA Infra on AWS (Hands-on) and Static Website on AWS (Theory)
What is Basic HTTP Authentication?
When I mention Basic HTTP Authentication, I’m not referring to more complex solutions like OAuth, JSON Web Tokens (JWT), or session-based authentication. Instead, Basic HTTP Authentication is a built-in browser mechanism, defined by RFC 7617, which provides a straightforward way to verify users by prompting for a username and password to access specific content.
How Does it Work?

Figure 1. Basic HTTP Authentication Workflow
The process typically follows this flow:
-
Initial Request: The client initiates a request to access protected content by sending an HTTP GET request. If the request lacks an Authorization header, CloudFront (or the server) detects this and denies access.
-
401 Unauthorized Response: The server responds with a 401 Unauthorized status, including a www-authenticate header. This header specifies Basic realm="Enter your credentials", prompting the client to provide credentials.
-
Credentials Prompt: The browser, upon receiving the 401 response with the www-authenticate header, prompts the user to enter a username and password.
-
Authorization Header with Credentials: After the user submits their credentials, the client constructs a new request with an Authorization header. This header contains the word "Basic" followed by a space and the Base64-encoded string of the format username:password.
-
Verification by CloudFront: The server (in this case, CloudFront) receives the request with the Authorization header, matches the Base64-encoded credentials, and verifies their validity.
-
Access Granted or Denied:
- If the credentials are valid, the server responds with HTTP/1.1 200 OK, granting access to the protected content.
- If the credentials are invalid, the server responds with another 401 Unauthorized status, prompting the client to re-enter correct credentials.
Although Basic HTTP Authentication credentials are encoded in Base64, they aren’t encrypted. To protect your credentials, always use HTTPS. If you’re using CloudFront, HTTPS is enforced automatically, as CloudFront distributions redirect HTTP requests to HTTPS.
Implementing Basic HTTP Authentication with CloudFront Edge Functions

Edge Functions
Edge functions are function handlers that are executed at edge locations before they reach the origin. There are currently two types of supported edge functions: Lambda@Edge functions and CloudFront functions.
For the use case of Basic HTTP Authentication, CloudFront Functions make perfect sense. They offer sub-millisecond execution times, making them faster than Lambda@Edge, and are also more cost-effective for this lightweight task. Since Basic HTTP Authentication typically involves simple header manipulation without complex logic.

CloudFront function workflow
When the viewer requests the content, the request is first directed to the edge location. If the content is cached in the edge location, it is served immediately. Otherwise, the edge location will forward the request to the origin (in this case, an S3 bucket). In our case, the CloudFront function will be executed at the edge location before the request is forwarded to the origin and the Basic HTTP Authentication logic will be executed.
How do you implement Basic HTTP Authentication with CloudFront?
Now that we understand how Basic HTTP Authentication works, let's learn a bit about CloudFront and S3 before we jump into the implementation details.
Amazon Simple Storage Service (S3) is a managed AWS service designed for storing and accessing various types of files, such as photos, documents, videos, and other content. Its reliability and scalability make it an excellent choice for hosting static website files. S3 can also serve as the origin for CloudFront distributions, the place where cloudfront gets its content from before it caches it at the edge locations and serves it to the users.
Amazon CloudFront is a managed content delivery network (CDN) that serves static and dynamic content to users via a global network of edge locations. When a user requests content served via CloudFront, the request is directed to the edge location with the lowest latency, providing the fastest response time for optimal performance. If the content is already cached in the closest edge location, CloudFront delivers it immediately. Otherwise, CloudFront retrieves the content from the specified origin e.g. an S3 bucket or an HTTP server.
The reason why we care about CloudFront is because it provides a way to process viewer requests (HTTP requests) at the edge locations before they reach the origin. This is where we can implement our Basic HTTP Authentication logic.
Let's take a look at the different types of edge functions that CloudFront supports. */}
Edge Functions

Figure 2. Edge Functions
Edge functions are function handlers that are executed at edge locations before they reach the origin. There are currently two types of supported edge functions: Lambda@Edge functions and CloudFront functions.
Lambda@Edge
Lambda@Edge extends AWS Lambda’s capabilities to run code closer to users, ideal for handling complex logic and full application functionality with high security. Lambda@Edge functions can run in either a Node.js or Python runtime environment. After publishing a function to a single AWS Region, associating it with a CloudFront distribution automatically replicates the code across AWS’s regional edge locations (57 locations globally).
Use Cases:
- Serving resized images on the fly based on the device type
- Authentication on the edge based on geolocation (e.g. country)
- Redirecting users to the closest regional edge location
- Cookie based split testing (has this user visited this page before?)
- Blocking certain IP addresses or regions
CloudFront Functions
Unlike Lambda@Edge, CloudFront Functions can only be written in JavaScript and run in a lightweight, latency-optimized runtime environment within CloudFront’s content delivery network. This setup is ideal for latency-sensitive tasks, offering submillisecond startup times and the ability to scale instantly. Cloudfront functions execute at "standard" edge locations (over 400 globally) which are closest to your viewers (users/clients).
Use Cases:
- Rewriting URLs for Single Page Applications (SPAs)
- Redirecting request using Basic HTTP Authentication
- Adding security headers
- IP address access control
- Redirection based on geolocation
- A/B testing e.g. redirect a small percentage of traffic to a new feature
- Lightweight bot or scraping detection based on the
User-Agent
header
Features Comparison Table
Here's a quick comparison of the features of CloudFront Functions and Lambda@Edge:
Feature | CloudFront Function | Lambda@Edge |
---|---|---|
Programming Language | JavaScript (ECMAScript 5.1 compliant) | Node.js and Python |
Execution Environment | Edge | Regional Edge |
Cost | Free tier, billed per request | No Free tier, billed per function execution and duration |
Use Case | For simple redirections/transformations | Logic Intensive |
Request Body Access | No | Yes |
Max Package Size (func & libs) | 10 KB | 1 MB (viewer request/response) - 50 MB (origin request/response) |
Function Memory | 2 MB | 128 MB (viewer request/response - 10,240 MB (origin request/response) |
Network Access | No | Yes (No VPC Access) |
Async/Await Support | No | Yes |
File System Access | No | Yes |
Execution Time | < 1ms | 5 - 30 seconds |
Metrics and Logging | Yes | Yes |
Cost Efficiency | Cheaper (1/6 price of Lambda@Edge) | More Expensive |
Pricing Model | Free tier, billed per request | No Free tier, billed per function execution and duration |
CloudFront Function Handler
Now that we understand a bit more about edge functions and how they work, let's take a look at the code.
// basic-http-auth.js
function btoa(input) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
input = String(input);
var bitmap,
a,
b,
c,
result = '',
i = 0,
rest = input.length % 3;
for (; i < input.length; ) {
if ((a = input.charCodeAt(i++)) > 255 || (b = input.charCodeAt(i++)) > 255 || (c = input.charCodeAt(i++)) > 255)
throw new TypeError(
"Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.",
);
bitmap = (a << 16) | (b << 8) | c;
result +=
b64.charAt((bitmap >> 18) & 63) +
b64.charAt((bitmap >> 12) & 63) +
b64.charAt((bitmap >> 6) & 63) +
b64.charAt(bitmap & 63);
}
return rest ? result.slice(0, rest - 3) + '==='.substring(rest) : result;
}
function handler(event) {
var authHeaders = event.request.headers.authorization;
var username = 'user';
var password = 'pass';
var expected = 'Basic ' + btoa(`${username}:${password}`);
if (authHeaders && authHeaders.value === expected) {
return event.request;
}
var response = {
statusCode: 401,
statusDescription: 'Unauthorized',
headers: {
'www-authenticate': {
value: 'Basic realm="Enter your credentials"',
},
},
};
return response;
}
The btoa() function
The btoa() function in this code is a custom implementation of Base64 encoding. It converts a given string (composed of Latin1 characters only) into a Base64-encoded string.
The handler function
This handler function enforces Basic HTTP Authentication by verifying the credentials provided in the request’s Authorization header. It begins by extracting the Authorization header and defining the expected credentials (in this case, "user:pass") encoded in Base64 format. If the provided Authorization header matches this expected value, the function allows the request to pass through to CloudFront or the origin server without modification. However, if the Authorization header is missing or doesn’t match, the function returns a 401 Unauthorized response, prompting the browser to display a login dialog where the user can enter the correct credentials.
Attaching the CloudFront Function to the Distribution
Here's how you can attach the CloudFront Function to the distribution using the AWS CDK. The actual implementation as you can see is very straightforward, you simply define the function, provide it the path to the function code, and then attach it to the distribution.
// website-stack.tsx
const websiteBucket = new s3.Bucket(this, `WebsiteBucket`, {
publicReadAccess: false,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const authFunction = new cloudfront.Function(this, 'BasicHttpAuthFunc', {
code: cloudfront.FunctionCode.fromFilePath({
filePath: path.join(__dirname, 'basic-http-auth.js'),
}),
});
new cloudfront.Distribution(this, `Distribution`, {
defaultBehavior: {
origin: cloudfrontOrigins.S3BucketOrigin.withOriginAccessControl(websiteBucket),
functionAssociations: [
{
function: authFunction,
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST,
},
],
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
defaultRootObject: 'index.html',
});
You can find the source code for this example in the GitHub repository.
Conclusion
Sometimes, the simplest solution is the ideal solution. Basic HTTP Authentication is a simple yet effective way to add a layer of protection to static sites hosted on S3 and delivered with CloudFront. By using CloudFront Functions, you get a fast, low-cost solution that’s perfect for handling lightweight tasks like this right at the edge. It’s a straightforward approach that covers your bases without adding unnecessary complexity.