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
}
}
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/