A Tale of SPA, AWS CloudFront, and Security Headers

Naresh Waswani
6 min readNov 4, 2021

I am sure many of you would have hosted Single Page Application (SPA) using Angular or React package deployed on AWS S3 and proxied via AWS CloudFront. It works like a charm!!!

But did you know that your application could be vulnerable to Frame Hijacking or Injection, Clickjacking and many more similar attacks if you did not follow some of the best practices to secure your application. Well, yes….I am talking about securing your application by adding Security Headers in the HTTP response of your application.

I have seen many folks who are not aware of such Security Headers or tend to forget adding these headers specially while hosting their SPA application using AWS CloudFront and S3 services. I also learnt it the hard way :( — when these security issues were discovered as part of Penetration Testing activity for one of the Client’s project I was working on.

You want to perform a quick test to check if your application is vulnerable to such attacks. There are many web sites who can help identify it. I generally hop on to — https://observatory.mozilla.org/.

Provide the URL of you site and it will provide result in no time. If your result look as below, then you need to tighten the security. How ??? Read the next section of the blog :)

Analysis of unsecured site using https://observatory.mozilla.org

This blog covers 2 approaches for implementing Security Headers with SPA —

  1. Use AWS Lambda@Edge to inject the Security headers when the CloudFront is about to respond back to the client
  2. Use CloudFront Functions to inject the Security headers when the CloudFront is about to respond back to the client

1)Using AWS Lambda@Edge —

Lambda@Edge is a feature of Amazon CloudFront that lets you run code closer to users of your application, which improves performance and reduces latency.

You can attach (associate) Lambda functions into CloudFront distribution. And these lambda functions are invoked as and when request hits CloudFront and response is about to be sent back to the caller. You can also configure Lambda function to be invoked when CloudFront makes request to the Origin and when the response is received from the Origin.

https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html

For details on how Lambda@Edge works and integrates with CloudFront, check this link.

We shall be using the Viewer Response association (hook) of CloudFront to attach Edge Lambda function. This association allows you to configure a Lambda function which will be executed* just before the response to the request is sent back to the client.

executed* — If any error occurs while processing the request on the CloudFront because of any reason, then the edge lambda function will skip the execution.

Snippet of the Lambda code that shall add Security Headers in the response —

'use strict';
exports.handler = (event, context, callback) => {

const response = event.Records[0].cf.response;
const headers = response.headers;
headers['x-xss-protection'] = [{key: 'X-XSS-Protection', value: '1; mode=block'}];
headers['strict-transport-security'] = [{key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubdomains'}];
headers['x-frame-options'] = [{key: 'X-Frame-Options', value: 'SAMEORIGIN'}];
headers['content-security-policy'] = [{key: 'Content-Security-Policy', value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"}];
headers['x-content-type-options'] = [{key: 'X-Content-Type-Options', value: 'nosniff'}];
callback(null, response);
};

And here is the sequence of steps to be performed —

  1. Add Lambda function and paste the code mentioned above.

2) Publish new version of the Lambda function as CloudFront expects a specific version of the Lambda function to be associated as Viewer Request/Response or Origin Request/Response.

3) Create S3 bucket and upload the SPA code to the bucket.

4) Create new AWS CloudFront distribution with S3 bucket as the origin and associate Lambda Function (specific version) as Viewer Response under Function Association configuration.

5) Wait for the distribution to be deployed. And that’s it!!!

Now, if you open the https://observatory.mozilla.org/ and test the application by providing the CloudFront distribution domain URL, you should see result as below —

Analysis of secured site using https://observatory.mozilla.org

And here is the result when I hosted a Test SPA with Security Headers configured —

Security Headers in the Browser’s Developer Tool

2)Using AWS CloudFront Functions —

CloudFront Functions is a new Serverless scripting platform that allows you to run lightweight JavaScript code at the edge.

At high level, the implementation approach is same as that of Edge Lambda. But there are few limitations in terms of size of the CloudFront functions, the programming language it supports and few other areas. Look at this link for more details.

Some of the key differences are —

  1. Lambda Edge supports Node.JS and Python as programming languages whereas CloudFront Functions supports only Javascript
  2. CloudFront Function can be associated only with Viewer Request and Response events whereas Lambda@Edge can be used for both Viewer and Origin Request/Response
  3. CloudFront Functions are highly scalable and cheaper compared to Lambda@Edge
  4. Logs from CloudFront Functions always goes to the US-East-1 region whereas Lambda@Edge sends logs to the Region closest to the location where it got executed.

Here is the CloudFront function snippet that can add Security Headers to the response —

function handler(event) {

var response = event.response;
var headers = response.headers;
headers['x-xss-protection'] = {value: '1; mode=block'};
headers['strict-transport-security'] = { value: 'max-age=31536000; includeSubdomains'};
headers['x-frame-options'] = {value: 'X-Frame-Options', value: 'SAMEORIGIN'};
headers['content-security-policy'] = { value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"};
headers['x-content-type-options'] = { value: 'nosniff'};
return response;
}

Sequence of steps to be performed with CloudFront Function —

1) Create CloudFront function and paste the code as mentioned above.

2) Publish the function.

3) Create S3 bucket and upload the SPA code to the bucket.

4) Create new AWS CloudFront distribution with S3 bucket as the origin and associate CloudFront Function as Viewer Response under Function Association configuration.

Associating Function to CloudFront

5) Wait for the distribution to be deployed. And that’s it!!! You will see the same result as highlighted in approach #1.

When to use which approach is probably the next question you must be asking??? Honestly, it depends :). If you have a need to make network calls from the Edge Function or need to perform some level of compute which will take lets say couple of seconds then Lambda@Edge is the option but if your use case is more of request/response manipulations, authentication and authorisation checks based on Request Headers or may be needs a quick calculation which can be done in milliseconds, CloudFront Function is the way to go.

Do take a look at the comparison page which will give you much more details in terms of what can be used and where???

Important Note — On 2nd Nov’21, AWS announced one more out of the box approach for handling the Security Headers. You can now configure Response Headers policies as part of CloudFront provisioning.

Will write another short blog capturing details on this new out of the box approach from AWS CloudFront.

Please do share this blog with you friends and don’t forget to give claps if this has helped you in any way.

Happy Blogging…Cheers!!!

--

--

Naresh Waswani

#AWS #CloudArchitect #CloudMigration #Microservices #Mobility #IoT