User Authentication with Lambda, CloudFront, and API Gateway

Project Overview

This guide explains how to implement user authentication for a static website hosted on Amazon S3, served through CloudFront, using a Lambda@Edge function. The Lambda function validates user credentials against a DynamoDB table, securing access to the site. While API Gateway can be integrated for API-based authentication, this project focuses on securing a static S3 site with CloudFront and Lambda@Edge.

Technologies Used

Lambda Function Code

import base64
                
import boto3
import os
from boto3.dynamodb.conditions import Key

dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('Users')

def lambda_handler(event, context):
    if os.getenv('LAMBDA_ENABLED', 'true').lower() != 'true':
        return {
            'status': '403',
            'statusDescription': 'Forbidden',
            'body': 'The service is currently disabled.'
        }

    request = event['Records'][0]['cf']['request']
    headers = request['headers']

    if 'authorization' in headers:
        try:
            auth_header = headers['authorization'][0]['value']
            encoded_credentials = auth_header.split(' ')[1]
            decoded_credentials = base64.b64decode(encoded_credentials).decode('utf-8')
            username, password = decoded_credentials.split(':')

            response = table.get_item(Key={'user_id': username})
            if 'Item' not in response:
                return unauthorized_response()

            user = response['Item']
            if password == user['password']:
                return request

        except Exception as e:
            print(f"[ERROR] Auth failed: {e}")

    return unauthorized_response()

def unauthorized_response():
    return {
        'status': '401',
        'statusDescription': 'Unauthorized',
        'headers': {
            'www-authenticate': [{'key': 'WWW-Authenticate', 'value': 'Basic'}]
        }
    }
            

Step-by-Step Guide

  1. Create a DynamoDB Table: Set up a DynamoDB table named 'Users' with 'user_id' as the partition key. Add an entry (e.g., {"user_id": "test@email.com", "password": "password@123"}).
  2. Develop the Lambda Function: Write a Python Lambda@Edge function (above) to handle Basic Authentication, querying DynamoDB for user credentials.
  3. Deploy Lambda@Edge: Deploy the function in us-east-1, publish a version (e.g., :4), and note the ARN.
  4. Configure CloudFront: Create a CloudFront distribution with your S3 bucket as the origin. Associate the Lambda function with the Viewer Request event for the Default (*) behavior.
  5. Update Cache Policy: Create a custom cache policy (e.g., 'AuthCachingPolicy') including the 'Authorization' header to ensure authentication isn’t bypassed by caching.
  6. Test Authentication: Access the CloudFront URL, enter credentials in the browser prompt, and verify access to the S3 site.

Challenges Faced

Final Outcome

The S3-hosted static website was successfully secured with user authentication using Lambda@Edge and CloudFront. Users must now provide valid credentials (stored in DynamoDB) to access the site, with the process fully integrated and functional.

Logs from Deployment

← Back to Resume