Search code examples
aws-lambdaamazon-cloudfrontaws-lambda-edge

Lambda@Edge Does Not See the `Authorization` Header


I wrote a lambda function (Node 20.x) that is supposed to control access to a CloudFront distribution hosting a static website with S3 as origin. At the moment, the function in index.mjs always returns a 401 and dumps the event structure in the response body:

export const handler = async (event, context) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    return {
        status: '401',
        statusDescription: 'Unauthorized',
        body: 'Unauthorized!\n' + JSON.stringify(event),
        headers: {
            'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
        },
    };
};

The problem is that the "Authorization" header is not included in the event although it is sent by the browser.

I have tried associating the function both with "Viewer Request" and "Origin Request" with the same result.

I have used the exact same setup two years ago with another combination of S3 and CloudFront, and it works there. I have compared the configurations of the two CloudFront distributions but I cannot see any significant differences.

How can my Lambda function access the "Authorization" header?

Below is the configuration of the distribution that I got with aws cloudfront get-distribution-config --id=DISTRIBUTION-ID:

{
    "ETag": "DISTRIBUTION-ID",
    "DistributionConfig": {
        "CallerReference": "some-uuid",
        "Aliases": {
            "Quantity": 1,
            "Items": [
                "problem.example.com"
            ]
        },
        "DefaultRootObject": "index.html",
        "Origins": {
            "Quantity": 1,
            "Items": [
                {
                    "Id": "problem.example.com.s3.us-east-1.amazonaws.com",
                    "DomainName": "problem.example.com.s3.us-east-1.amazonaws.com",
                    "OriginPath": "",
                    "CustomHeaders": {
                        "Quantity": 0
                    },
                    "S3OriginConfig": {
                        "OriginAccessIdentity": "origin-access-identity/cloudfront/OAI-ID"
                    },
                    "ConnectionAttempts": 3,
                    "ConnectionTimeout": 10,
                    "OriginShield": {
                        "Enabled": false
                    },
                    "OriginAccessControlId": ""
                }
            ]
        },
        "OriginGroups": {
            "Quantity": 0
        },
        "DefaultCacheBehavior": {
            "TargetOriginId": "problem.example.com.s3.us-east-1.amazonaws.com",
            "TrustedSigners": {
                "Enabled": false,
                "Quantity": 0
            },
            "TrustedKeyGroups": {
                "Enabled": false,
                "Quantity": 0
            },
            "ViewerProtocolPolicy": "https-only",
            "AllowedMethods": {
                "Quantity": 3,
                "Items": [
                    "HEAD",
                    "GET",
                    "OPTIONS"
                ],
                "CachedMethods": {
                    "Quantity": 3,
                    "Items": [
                        "HEAD",
                        "GET",
                        "OPTIONS"
                    ]
                }
            },
            "SmoothStreaming": false,
            "Compress": true,
            "LambdaFunctionAssociations": {
                "Quantity": 1,
                "Items": [
                    {
                        "LambdaFunctionARN": "arn:aws:lambda:us-east-1:SOME-NUMBER:function:myFunctionName:7",
                        "EventType": "viewer-request",
                        "IncludeBody": false
                    }
                ]
            },
            "FunctionAssociations": {
                "Quantity": 0
            },
            "FieldLevelEncryptionId": "",
            "ForwardedValues": {
                "QueryString": false,
                "Cookies": {
                    "Forward": "none"
                },
                "Headers": {
                    "Quantity": 1,
                    "Items": [
                        "Authorization"
                    ]
                },
                "QueryStringCacheKeys": {
                    "Quantity": 0
                }
            },
            "MinTTL": 0,
            "DefaultTTL": 60,
            "MaxTTL": 600
        },
        "CacheBehaviors": {
            "Quantity": 0
        },
        "CustomErrorResponses": {
            "Quantity": 0
        },
        "Comment": "",
        "Logging": {
            "Enabled": true,
            "IncludeCookies": false,
            "Bucket": "logging.example.com.s3.amazonaws.com",
            "Prefix": "problem"
        },
        "PriceClass": "PriceClass_100",
        "Enabled": true,
        "ViewerCertificate": {
            "CloudFrontDefaultCertificate": false,
            "ACMCertificateArn": "CERTIFICATE-ARN",
            "SSLSupportMethod": "sni-only",
            "MinimumProtocolVersion": "TLSv1.2_2021",
            "Certificate": "CERTIFICATE-ARN",
            "CertificateSource": "acm"
        },
        "Restrictions": {
            "GeoRestriction": {
                "RestrictionType": "none",
                "Quantity": 0
            }
        },
        "WebACLId": "",
        "HttpVersion": "http2",
        "IsIPV6Enabled": true,
        "ContinuousDeploymentPolicyId": "",
        "Staging": false
    }
}

Solution

  • The authorization header is no longer exposed to Lambda@Edge functions, when using recent Node.js environments (Node.js 20.x).

    The alternative is using signed cookies. The procedure is a little bit more complicated but a lot more flexible and user-friendly. I have created a blog-post with detailed instructions here: https://www.guido-flohr.net/authenticating-access-to-private-content-hosted-with-aws-cloudfront/