Search code examples
amazon-web-servicesamazon-cloudfront

Add X-Frame-Options header to all URLs using CloudFront functions


I have a single page application and I'm trying to prevent clickjacking by adding X-Frame-Options header to the HTML responses. My website is hosted on S3 through CloudFront.

The CloudFront distribution is configured to send index.html by default:

Default root object
index.html

In the Error Pages section I configured 404 page to also point to the index.html. This way all URLs that are not in S3 return the default HTML, i.e. /login, /admin etc.

404 error page config

Update The 403 status code is also configured: 403 error page config

Then I have created a CloudFront function as described here and assigned it to the Viewer response:

function handler(event) {
    var response = event.response;
    var headers = response.headers;

    headers['x-frame-options'] = {value: 'DENY'}; 

    return response;
}

This works, but only for /:

curl -v https://<MYSITE.com>
....
< x-frame-options: DENY

For other URLs it doesn't work - the x-frame-options header is missing:

curl -v https://<MYSITE.com>/login
....
< x-cache: Error from cloudfront

My question is - why my cloudfront function does not append a header in the error response, and what can I do to add it?


Solution

  • I understand that your questions are:

    • Q1: Why does the CloudFront function work for /?
    • Q2: Why doesn't the CloudFront function work for other url path?

    Please refer to the responses below:

    • A1: Since you might specify a Default Root Object [1] (e.g.index.html) which returning the object when a user requests the root URL. When CloudFront returns the object with 200 ok, the CloudFront Function will be invoked on the viewer response event.
    • A2: You might not give the s3:ListBucket permissions in your S3 bucket policy(e.g. OAI). As the result, you will get Access Denied(403) errors for missing objects instead of 404 Not Found errors. Namely, the Error Pages you have configured isn't applied to this case, and the CloudFront Function won't be invoked because the HTTP status code is higher than 399[2].

    [Updated] Suggestion:

    • Since CloudFront does not invoke edge functions for viewer response events when the origin returns HTTP status code 400 or higher. However, Lambda@Edge functions for origin response events are invoked for all origin responses. In this senario, I'll suggest that we should use Lambda@Edge instead of CloudFront Functions.
    • For your convenience, please refer to the sample code of l@e:
    exports.handler = async (event, context) => {
        const response = event.Records[0].cf.response;
        const headers = response.headers;
       
        headers['x-frame-options'] = [{
            key: 'X-Frame-Options',
            value: 'DENY',
        }];
    
        return response;
    };
    
    • FYI. Here is my curl test result:
    # PATH: `/`
    $ curl -sSL -D - https://dxxxxxxx.cloudfront.net/
    HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: 12
    Connection: keep-alive
    ETag: "e59ff97941044f85df5297e1c302d260"
    ___snipped___
    Server: AmazonS3
    X-Frame-Options: DENY
    ___snipped___
    
    # PATH: `/login`
    $ curl -sSL -D - https://dxxxxxxx.cloudfront.net/login
    HTTP/1.1 200 OK
    Content-Type: text/html
    Content-Length: 12
    Connection: keep-alive
    ETag: "e59ff97941044f85df5297e1c302d260"
    ___snipped___
    Server: AmazonS3
    X-Frame-Options: DENY
    ___snipped___