Learn how to host static assets in S3, serve them globally via CloudFront with Origin Access Control, and enforce Basic Authentication at edge locations using Lambda@Edge.
Secure Static Content Hosting with S3, CloudFront, and Basic Authentication
This guide demonstrates how to:
- Host static assets in an AWS S3 bucket
- Serve them globally via CloudFront with an Origin Access Control (OAC)
- Enforce Basic Authentication at edge locations using Lambda@Edge
- Secure access through precise IAM policies
Step 1: Create and Secure the S3 Bucket
1.1 Create a New S3 Bucket
- Console: S3 > Create bucket.
- Bucket name:
your-docs-bucket(must be globally unique). - Region:
us-east-1(required for Lambda@Edge). - Uncheck "Block all public access." We lock it down with CloudFront instead.
1.2 Configure Bucket Policy
Allow only CloudFront to fetch objects. Replace placeholders with actual IDs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "cloudfront.amazonaws.com" },
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-docs-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD632BHDS5"
}
}
}
]
}
Step 2: Set Up CloudFront Distribution
2.1 Create a Distribution
- Console: CloudFront > Create distribution.
- Origin domain:
your-docs-bucket.s3.us-east-1.amazonaws.com - Origin access: Origin Access Control (OAC) -- create a new OAC.
- Viewer protocol policy: Redirect HTTP to HTTPS.
- Default root object:
index.html. - Alternate domain name (CNAME):
docs.example.com(if using a custom domain).
2.2 Configure Origin Access Control (OAC)
- After distribution creation, go to the Origins tab.
- Select the origin > Edit > Origin access > Apply the new OAC.
- This replaces legacy Origin Access Identity (OAI).
CloudFront will update the bucket policy OAC principal automatically if the option is selected.
Step 3: Implement Basic Authentication with Lambda@Edge
3.1 Create IAM Role for Lambda@Edge
The Lambda function needs a role that trusts both Lambda and CloudFront:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
"Action": "sts:AssumeRole"
}
]
}
Name: lambda-edge-basic-auth-role
3.2 Attach Execution Policy
Grant CloudWatch Logs access for debugging:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
3.3 Write the Lambda Function
- Console: Lambda > Create function > Author from scratch.
- Name:
basic-auth-edge, Runtime: Node.js 18.x, Region:us-east-1. - Attach Role:
lambda-edge-basic-auth-role.
Use this code:
'use strict';
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
const headers = request.headers;
const authUser = 'admin';
const authPass = 'P@ssw0rd!';
const authString = 'Basic ' + Buffer.from(authUser + ':' + authPass).toString('base64');
if (
typeof headers.authorization === 'undefined' ||
headers.authorization[0].value !== authString
) {
return {
status: '401',
statusDescription: 'Unauthorized',
body: 'Unauthorized',
headers: {
'www-authenticate': [{ key: 'WWW-Authenticate', value: 'Basic realm="Protected"' }],
},
};
}
return request;
};
3.4 Publish and Version Lambda
- Publish a new version from the Lambda console.
- Note the version ARN:
arn:aws:lambda:us-east-1:123456789012:function:basic-auth-edge:1.
3.5 Attach Lambda@Edge to CloudFront
- CloudFront > Distributions > Edit behavior > Function associations.
- Select Viewer Request and paste the version ARN.
- Save and deploy. Edge locations will propagate in minutes.
Step 4: Final Verification
- Visit
https://docs.example.com. A Basic Auth prompt should appear. - Enter credentials to proceed.
- Attempt direct S3 URL access (
https://your-docs-bucket.s3.amazonaws.com/index.html) -- it should return Access Denied.
Final Notes
The setup is now:
- Scalable S3 storage with no direct public access
- Global CloudFront CDN with HTTPS
- Edge-level Basic Authentication via Lambda@Edge
Security Tip: Rotate Basic Auth credentials periodically and monitor CloudFront logs (S3 bucket logs or Lambda@Edge logs) for unauthorized attempts. For production use cases with more complex authentication requirements, consider integrating with Cognito or an external identity provider instead of Basic Auth.
Or read how we handle it in AWS Cloud Management.
Related Articles
How to Migrate a Server to AWS Without a Big Bang Cutover
A big bang cutover is a plan with exactly one attempt in it. The incremental version costs a little more elapsed time and keeps a working rollback available until the very last step. This walks through the inventory that decides whether the cutover is clean, continuous replication that runs while the old server keeps serving, a dress rehearsal you can repeat, the DNS time to live arithmetic you have to do backwards from the cutover date, and the single action that ends the rollback window for good.
Server & DevOpsHow to Do Canary Releases Without a Service Mesh
You can send five percent of production traffic at a new version, watch the error rate, and roll back in seconds without installing a service mesh. This walks through replica weighted canaries with a progressive delivery controller, real percentage splitting at the edge, an automated pass or fail check against Prometheus, and the rollback path. It also covers what changed when Kubernetes retired Ingress NGINX in March 2026.
Server & DevOpsHow to Troubleshoot and Fix Cloudflare Error 521 on Ubuntu
A practical Ubuntu-focused guide to diagnosing and resolving Cloudflare Error 521, covering origin server checks, web service restarts, firewall rules, resource monitoring, and prevention strategies.