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.
Update The 403 status code is also configured:
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?
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___
Having a hard time with setting custom headers up to prevent cross side scripting on an Amplify App.
This is the configuration:
customHeaders:
- pattern: '**/*'
headers:
- key: Strict-Transport-Security
value: max-age=31536000; includeSubDomains
- key: X-Frame-Options
value: deny
- key: X-XSS-Protection
value: 1; mode=block
- key: X-Content-Type-Options
value: nosniff
- key: Content-Security-Policy
value: frame-ancestors 'none'
When loading the site in an iframe like so:
<html>
<head></head>
<body>
<h1>IFRAME blocked</h1>
<iframe width=100% height=100% src="https://foo.bar/"></iframe>
</body>
</html>
Then the iFrame is successfully blocked.
But when loading the iframe like this:
<html>
<head></head>
<body>
<h1>IFRAME Not blocked</h1>
<iframe width=100% height=100% src="https://foo.bar/login"></iframe>
</body>
</html>
Then the iFrame is rendering the page.
Any ideas how to extend the custom header configuration to also include any additional "path" of the url?
Please try this
X-Frame-Options: ALLOW-FROM URL
Details
X-Frame-Options: DENY It completely denies to be loaded in
frame/iframe.
X-Frame-Options: SAMEORIGIN It allows only if the site which wants
to load has a same origin.
X-Frame-Options: ALLOW-FROM URL It grants a specific URL to load
itself in a iframe. However please pay attention to that, not all
browsers support this.
I am calling an AWS API which uses lambda function. I am calling it from a HTML page which is hosted in S3 (static web hosting). While calling the API I get CORS error:
Access to XMLHttpRequest at '' from origin
'' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
I have tried it after enabling CORS on API, Specifying CORS on S3 bucket but it is not working.
Seems, I am missing the right place where CORS headers are to be specifies.
Additional information:
I get following information in chrome developer tool
**Response Headers**
content-length: 42
content-type: application/json
date: Mon, 24 Feb 2020 04:28:51 GMT
status: 403
x-amz-apigw-id: IYmYgFQvoAMFy6Q=
x-amzn-errortype: MissingAuthenticationTokenException
x-amzn-requestid: 79b18379-383d-4ddb-a061-77f55b5727c3
**Request Headers**
authority: apiid-api.us-east-1.amazonaws.com
method: POST
path: /Prod
scheme: https
accept: application/json, text/javascript, */*; q=0.01
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,hi;q=0.8
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
access-control-allow-methods: GET, OPTIONS
access-control-allow-origin: https://apiid.execute-api.us-east-1.amazonaws.com/Prod
content-length: 117
content-type: application/json; charset=UTF-8
origin: http://example.com
referer: http://example.com/
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Lambda function code:
var AWS = require('aws-sdk');
var ses = new AWS.SES();
var RECEIVER = 'example#gmail.com';
var SENDER = 'example#gmail.com';
var response = {
"isBase64Encoded": false,
"headers": { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'},
"statusCode": 200,
"body": "{\"result\": \"Success.\"}"
};
exports.handler = function (event, context) {
console.log('Received event:', event);
sendEmail(event, function (err, data) {
context.done(err, null);
});
};
function sendEmail (event, done) {
var params = {
Destination: {
ToAddresses: [
RECEIVER
]
},
Message: {
Body: {
Text: {
Data: 'name: ' + event.name + '\nphone: ' + event.phone + '\nemail: ' + event.email + '\ndesc: ' + event.desc,
Charset: 'UTF-8'
}
},
Subject: {
Data: 'Website Referral Form: ' + event.name,
Charset: 'UTF-8'
}
},
Source: SENDER
};
ses.sendEmail(params, done);
}
No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is saying that the resource you requested, your Lambda via API Gateway, is not returning an Access-Control-Allow-Origin header in its response; the browser is expecting the CORS headers in the response from the API (possibly because of an OPTIONS request), but the response doesn’t have them.
You’ve not said so specifically but I’m assuming you’re using a Lambda proxy integration on your API gateway. To solve your issue, add a Access-Control-Allow-Origin: * header to the response your Lambda returns. You’ve not specified the language your Lambda is written in, or provided your Lambda code, but if it was in NodeJS, a snippet of what you‘d return might look something like:
const result = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
// other required headers
},
body: object_you_are_returning
};
return result;
Very surprisingly, the reason seems to be “the client-side shouldn't have 'Access-Control-Allow-Origin': '*'. That should only be in the Lambda code, apparently, and adding it to the jQuery code will cause the preflight to fail for some reason.”
https://github.com/serverless/serverless/issues/4037#issuecomment-320434887
Supplement information following the comment:
I also tried to use ajax hosted in S3 to call my Lambda function through API Gateway. I have tried many suggestions especially this solution (http://stackoverflow.com/a/52683640/13530447), but none of them works for me. In the developer-mode of Chrome, I kept seeing the error "Access to XMLHttpRequest at '[my API]' from origin '[my S3 website]' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response." I solved this by removing 'Access-Control-Allow-Origin': '*' in ajax. Still take the solution (http://stackoverflow.com/a/52683640/13530447) as an example, it will work by changing
$.ajax(
{
url: 'https://npvkf9jnqb.execute-api.us-east-1.amazonaws.com/v1',
headers: {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'
},
crossDomain: true,
type:'GET',
dataType: 'text',
success: function(data)
{
window.alert(data);
}
});
into
$.ajax(
{
url: 'https://npvkf9jnqb.execute-api.us-east-1.amazonaws.com/v1',
headers: {'Content-Type': 'application/json'},
crossDomain: true,
type:'GET',
dataType: 'text',
success: function(data)
{
window.alert(data);
}
});
I'm build an API using AWS SAM (Lambda & API Gateway) whose contract is defined by a 3rd party.
The 3rd party calls my API with a GET request that contains JSON in the body. However, when a request is sent to the API with a body it gets rejected by CloudFront.
This is the request:
curl -X GET -H "Content-Type: application/json" --data '{"hello":"world"}' https://my-api.execute-api.us-east-2.amazonaws.com/Prod/my-api
This is the response:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD>
<BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Bad request.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: 1p0St_-e3noQL-2uMxeB_2I6lkMr1mg5afvxJRmVpCdnG67Vgnhj9w==
</PRE>
<ADDRESS></ADDRESS>
</BODY>
</HTML>
Checking the logs, the request never hits API Gateway or the Lambda function. However, if I remove the body from the request, then it hits the Lambda function and I get the appropriate error message from the API (telling the caller that the expected body is missing.)
curl -X GET -H "Content-Type: application/json" https://my-api.execute-api.us-east-2.amazonaws.com/Prod/my-api
I'm using basic configuration of API Gateway via a SAM template. This is the relevant section:
MyApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: bin/main.zip
Handler: main
Runtime: go1.x
Tracing: Active
Role: !Sub ${MyApiLambdaExecutorRole.Arn}
Events:
CatchAll:
Type: Api
Properties:
Path: /my-api
Method: GET
GET requests cannot contain a request body on CloudFront. Try using POST instead.
If you want to send limited data in a GET request, you can use query parameters.
You can see in the AWS Docs that this isn't possible here:
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#RequestCustom-get-body
If a viewer GET request includes a body, CloudFront returns an HTTP status code 403 (Forbidden) to the viewer.
You could perhaps use an EC2 instance or other service that doesn't use API Gateway to handle the request.
Every time i do a GET request to API from FrontEnd or POSTMAN to secured (isAuthenticated) content, i get 401 error(Unauthorized).
I have two servers:
1.Django, django-rest-framework, with Json Web Token.(API)
2.Vue.js
api endpoints:
Registration (AllowAny): http://holykrava.hopto.org:8002/user-api/register/
Login (AllowAny): http://holykrava.hopto.org:8002/user-api/get-token/
List of Users (IsAuthenticated): http://holykrava.hopto.org:8002/user-api/user-list/
Vue-Part
<script>
//import {HTTP} from './http-common';
import axios from 'axios'
export default {
created() {
//let tokenOld = localStorage.getItem('token')
let token = this.$store.state.token
axios.get(
'http://holykrava.hopto.org:8002/user-api/user-list/',
{headers: {
authorization: token
}}
)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
})
}
}
</script>
Chrome Dev Tool (Network-Logs)
General-Headers:
Request URL: http://holykrava.hopto.org:8002/user-api/user-list/
Request Method: GET
Status Code: 401 Unauthorized
Remote Address: 188.190.62.145:8002
Referrer Policy: no-referrer-when-downgrade
Request-Headers:
Provisional headers are shown
Accept: application/json, text/plain, */*
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im11dGFwdXRhIiwiZXhwIjoxNTMxODUxODg0LCJlbWFpbCI6IiIsIm9yaWdfaWF0IjoxNTMxNzY1NDg0fQ.LGji_eCDVIvGpQ9xDEO2QAISEEW9FpHVcwRl6Oz9cCM
Origin: http://localhost:8080
Referer: http://localhost:8080/?
Response-Headers:
Access-Control-Allow-Origin: *
Allow: GET, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Mon, 16 Jul 2018 19:34:45 GMT
Server: WSGIServer/0.2 CPython/3.6.4
Vary: Accept
WWW-Authenticate: JWT realm="api"
X-Frame-Options: SAMEORIGIN
IT Works in Terminal like this:
get token:
curl -X POST -d "username=userusername&password=userpassword" http://holykrava.hopto.org:8002/user-api/get-token/
pass token:
curl -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0MCwidXNlcm5hbWUiOiJ1c2VydXNlcm5hbWUiLCJleHAiOjE1MzE4NDAxNTAsImVtYWlsIjoiIiwib3JpZ19pYXQiOjE1MzE3NTM3NTB9.SoITxagpmbviEFl_Iy086Jy0KgAUNV0WW-a3wMM_Fos" http://holykrava.hopto.org:8002/user-api/user-list/
corsheaders.middleware.CorsMiddleware, installed
CORS_ORIGIN_ALLOW_ALL = True
Default Authintication Class:
'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
i spend two days trying to figure out whats wrong, but failed
looking for some1 who can help! thanks.