Simple basic auth in AWS Cloudfront with serverless
AWS Cloudfront is a CDN service by Amazon which is used to efficiently host Single Page Applications inside of AWS from a huge distributed network from nodes that are closest to the user.
Serverless is a free and open-source web framework for easy deployments in the cloud. It allows easy to create a deployment using CloudFront, but sometimes you might want to implement basic authentication for your deployed web apps to prevent search engines look at your site under development or just keep it under private access.
Due to its nature, CloudFront serves your content from different servers all over the world. Only one way to implement the ability to ask a user for basic auth is to apply special "Edge" Lambdas, which are uploaded to every server.
This post shows the most simple and working solution for CloudFront basic Auth using Lambda@Edge.
Since lambda functions for Lambda@Edge should be deployed to us-east-1
region we recommend to upload all stack in us-east-1
– content anyway will be served from distributed servers which will be located closer to a user so you don't have to worry about ping times.
resources:
Resources:
StaticSite:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
BucketName: mybucketname
WebsiteConfiguration:
IndexDocument: index.html
SiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: StaticSite
PolicyDocument:
Statement:
- Effect: Allow
Principal: "*"
Action: s3:GetObject
Resource:
Fn::Join:
- ""
- - "arn:aws:s3:::"
- Ref: StaticSite
- "/*"
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Aliases:
- "mydomain.com"
Origins:
- DomainName: mybucketname.s3-website.${self:provider.region}.amazonaws.com
Id: WebApp
CustomOriginConfig:
HTTPPort: '80'
HTTPSPort: '443'
OriginProtocolPolicy: http-only
Enabled: true
Comment: PLAY
DefaultRootObject: index.html
PriceClass: PriceClass_100
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
TargetOriginId: WebApp
ForwardedValues:
Cookies:
Forward: none
QueryString: true
SmoothStreaming: false
LambdaFunctionAssociations:
- EventType: 'viewer-request'
LambdaFunctionARN: !Join ["" , [!GetAtt BasicAuthEdgeLambda.Arn, ":1"]]
ViewerProtocolPolicy: redirect-to-https
ViewerCertificate:
MinimumProtocolVersion: TLSv1
SslSupportMethod: sni-only
AcmCertificateArn: ${self:custom.sslCertARN}
EdgeLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: 'sts:AssumeRole'
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com
- replicator.lambda.amazonaws.com
Effect: Allow
Policies:
- PolicyName: EdgePoliciesLambdaPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'lambda:GetFunction'
- 'lambda:EnableReplication*'
- 'lambda:InvokeFunction'
- 'iam:CreateServiceLinkedRole'
- 'cloudfront:UpdateDistribution'
- 'cloudfront:CreateDistribution'
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
- 'xray:PutTraceSegments'
- 'xray:PutTelemetryRecords'
Effect: Allow
Resource: '*'
BasicAuthEdgeLambda:
Type: 'AWS::Lambda::Function'
Properties:
Handler: 'index.beforeOriginRequest'
Role: !GetAtt EdgeLambdaRole.Arn
Code:
ZipFile: !Sub "
'use strict';
const path = require('path');
exports.beforeOriginRequest = (event, context, callback) => {
const authUser = 'admin';
const authPass = 'letmein';
const request = event.Records[0].cf.request;
const headers = request.headers;
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
callback(null, {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}],
}
});
}
callback(null, request);
};
"
Runtime: 'nodejs12.x'
Timeout: '5'
TracingConfig:
Mode: 'Active'
BasicAuthEdgeLambdaVersion:
Type: 'AWS::Lambda::Version'
Properties:
FunctionName:
Ref: BasicAuthEdgeLambda
You can configure credentials in const authUser = 'admin'; const authPass = 'letmein';
.
Keep it simple and keep it secure.