Skip to main content
Back to Blog
Server & DevOpsFebruary 17, 202510 min read

Secure Static Content Hosting with S3, CloudFront, and Basic Authentication

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.

OPS

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.

Need help with this?

Our team handles this kind of work daily. Let us take care of your infrastructure.

Related Articles