CORS impossible on AWS Lambda HTTP API Gateway Integration - amazon-web-services

An AWS Lamba function (NodeJS) returning 3 HTTP headers: aaa, Access-Control-Allow-Origin and bbb was created:
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers: { "aaa":"aaa", "Access-Control-Allow-Origin":"*", "bbb":"bbb" },
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
The function is integrated into a HTTP API (not REST API). In the HTTP API Gateway Configuration, Section "Configure CORS", the HTTP header "Access-Control-Allow-Origin" was set to "*". Please see the screenshot:
Gateway Config
The command "curl -i https://xxxxxxxxxx.execute-api.eu-central-1.amazonaws.com" proves that the HTTP Header Access-Control-Allow-Origin is explicitly removed, because only HTTP headers aaa and bbb are returned:
HTTP/2 200
date: Tue, 14 Apr 2020 11:01:58 GMT
content-type: text/plain; charset=utf-8
content-length: 20
aaa: aaa
bbb: bbb
apigw-requestid: K-S2EjVWliAEJKw=
Why on earth is this header still not present, even after "Configure CORS" was done?
(I'm googling now for more than two days in order to find a solution and it makes me go nuts)

As per Configuring CORS for an HTTP API -
If you configure CORS for an API, API Gateway ignores CORS headers
returned from your backend integration.
That's why the CORS headers from your Lambda (integration) are being ignored. This is one of the differences between the new HTTP APIs from the original REST APIs. In case of these APIs -
For a CORS request, API Gateway adds the configured CORS headers to
the response from an integration.
When you do a simple curl, that is not actual doing a cross-origin request. Hence, you don't see the CORS headers that would be set by the HTTP API. To verify if a CORS request works, I passed an Origin header in the below request and I can see the CORS headers along with my custom headers from Lambda -
$ curl -v -X GET https://$API_ID.execute-api.$AWS_REGION.amazonaws.com -H "Origin: https://www.example.com"
< HTTP/2 200
< date: Tue, 14 Apr 2020 18:02:26 GMT
< content-type: text/plain; charset=utf-8
< content-length: 18
< aaa: aaa
< bbb: bbb
< access-control-allow-origin: https://www.example.com
< access-control-expose-headers: date, x-api-id
Below is a snippet of my CORS configuration on the API. I added Access-Control-Allow-Origin value as https://www.example.com and passed this as a part of the Origin header in my curl request. Such a request would qualify as CORS.

For POST/PUT requests, you'll need to white list the content-type header.
Putting the wildcard doesn't do the trick for some reason, you need to explicitly whitelist it.

For Googlers:
If your OPTIONS preflights succeed but no Access-Control- headers present, and if you are testing using CURL, take extra attention to spell required headers for a preflight:
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
Origin: https://theaws.blog
If wrongly spelt, OPTIONS succeeds with 204 No Content, but no Access-Control- headers thus render the preflight result invalid.
Also check you have enough scope for the parameters, as long as your requested method and headers are a subset of the parameter, you will get Access-Control- headers. If not, you will get 204 No Content instead, which I would say it's not very informative!

Related

How to enable CORS in AWS API Gateway

I'm trying to enable CORS with Amazon's API gateway and Lambda, but I keep getting the error.
Access to XMLHttpRequest at 'https://<url>' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I've followed https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html and added this to lambda expression:
statusCode : statusCode,
body: responseBody,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
}
I've also gone to the API gateway and clicked the enable CORS button. If I look at the OPTIONS in the API gateway it says the
Access-Control-Allow-Headers
is present
If I run a test on the API gateway I can see the headers returned by the lambda expression and the expected headers are all there, but I'm still getting the same error message.
edit:
Logwatch isn't printing anything so I'm assuming that it's not even making it to my lambda function.
I can hit the API successfully from POSTMAN and it has the CORS headers
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: OPTIONS,POST,GET
Here are the response headers I'm getting when I try in chrome.
content-length: 35
content-type: application/json
date: Sun, 03 May 2020 01:51:15 GMT
status: 400
x-amz-apigw-id: L7p_IEbuoAMFeaA=
x-amzn-errortype: BadRequestException
x-amzn-requestid: 7ed06b7d-951f-4774-9bfa-62f307ee5974
The problem is that I was POSTing the data incorrectly from my UI so it was failing model validation. After I disabled body validation it started working properly.
See this question:
AWS API Gateway - CORS + POST not working

AWS S3 PUT Example using REST API

The AWS S3 PUT REST API docs are lacking a clear example of the Authorization string in the Request Syntax.
Request Syntax
PUT /Key+ HTTP/1.1
Host: Bucket.s3.amazonaws.com
x-amz-acl: ACL
Cache-Control: CacheControl
Content-Disposition: ContentDisposition
Content-Encoding: ContentEncoding
Content-Language: ContentLanguage
Content-Length: ContentLength
Content-MD5: ContentMD5
Content-Type: ContentType
Expires: Expires
x-amz-grant-full-control: GrantFullControl
x-amz-grant-read: GrantRead
x-amz-grant-read-acp: GrantReadACP
x-amz-grant-write-acp: GrantWriteACP
x-amz-server-side-encryption: ServerSideEncryption
x-amz-storage-class: StorageClass
x-amz-website-redirect-location: WebsiteRedirectLocation
x-amz-server-side-encryption-customer-algorithm: SSECustomerAlgorithm
x-amz-server-side-encryption-customer-key: SSECustomerKey
x-amz-server-side-encryption-customer-key-MD5: SSECustomerKeyMD5
x-amz-server-side-encryption-aws-kms-key-id: SSEKMSKeyId
x-amz-server-side-encryption-context: SSEKMSEncryptionContext
x-amz-request-payer: RequestPayer
x-amz-tagging: Tagging
x-amz-object-lock-mode: ObjectLockMode
x-amz-object-lock-retain-until-date: ObjectLockRetainUntilDate
x-amz-object-lock-legal-hold: ObjectLockLegalHoldStatus
Body
The docs show this request example further on...
PUT /my-image.jpg HTTP/1.1
Host: myBucket.s3.<Region>.amazonaws.com
Date: Wed, 12 Oct 2009 17:50:00 GMT
Authorization: authorization string
Content-Type: text/plain
Content-Length: 11434
x-amz-meta-author: Janet
Expect: 100-continue
[11434 bytes of object data]
But again, the doc does not have an example format for Auth String. I tried AccessKeyID Secret but that didn't work. I dont' even see logical parameters in the request syntax to pass the two parts of the credential (AccessKeyID and Secret) anywhere in the examples!
Does anyone have a simple example of how to use PUT to add a .json file to S3 using the REST API? Preferrably a screenshot of PostMan setup to better explain where values go (in URL vs. as headers).
From the AWS docs here, it appears it is not possible to create a PUT request to an S3 bucket using REST API alone:
For authenticated requests, unless you are using the AWS SDKs, you have to write code to calculate signatures that provide authentication information in your requests.
This is a new concept to me. I've used token requests and sending keys in headers before when authenticating via REST API's. It sounds like a more secure method of auth.

What Access-Control-* headers to send back in response to CORS preflight OPTIONS request and then subsequent GET/POST/etc. request?

I'm setting up a custom authorization workflow on API Gateway and looking for insights on CORS.
I have an authorizer that returns an IAM role based on validation of a barertoken - that then invokes the lambda my target endpoint which returns the response as desired.
I've made this work by adding my barer token to both the OPTIONS method and the GET/POST methods.
In general, I'm adding most CORS headers to the OPTIONS + GET/POST/etc methods, but this seems superfluous.
I'm a little outside of my depth here, but intuitively - if I needed to add the exact same headers in my OPTIONS method and the target method I think AWS would simply aggregate the headers from the target methods into the preflight options config, so I assume I'm adding the headers unnecessarily somewhere, but I'm not sure.
Could someone please provide an overview / walk-through of how headers are being passed by API Gateway on a CORS api call?
example info:
endpoint: api.example.com/logout
methods: OPTIONS, GET
Currently I have:
OPTIONS/GET:
request headers:
> barerToken
response headers:
> set-Cookie: integration.response.body.payload.httpCookie
> Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,barerToken'
> Access-Control-Allow-Origin: 'https://example.com'
> Access-Control-Allow-Credentials: 'true'
> set-cookie: integration.response.body.payload.cookie
> Access-Control-Allow-Methods: 'OPTIONS,POST'
My intuition says I should only need the following:
OPTIONS:
request headers:
> barerToken
response headers:
> Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,barerToken'
> Access-Control-Allow-Origin: 'https://example.com'
> Access-Control-Allow-Credentials: 'true'
> Access-Control-Allow-Methods: 'OPTIONS,POST'
GET:
response headers:
> set-Cookie: integration.response.body.payload.httpCookie
> set-cookie: integration.response.body.payload.cookie
My understanding is that the flow is as follows:
Browser (Preflight) -> End Point (OPTIONS) -> Browser (Request) -> Authorizer Lambda -> End Point (GET) -> Browser (Response)
OR
Browser (Preflight) -> Authorizer Lambda -> End Point (OPTIONS) -> Browser (Request) -> End Point (GET) -> Browser (Response)
But I could easily be mistaken.
Lastly, should I only apply the authorizer to the OPTIONS method or should it be applied to the OPTIONS and GET methods? What are the advantages/disadvantages of one versus both?
I'm adding most CORS headers to the OPTIONS + GET/POST/etc methods, but this seems superfluous.
The Access-Control-Allow-Origin response header is never superfluous. It must be included in all responses, regardless of the HTTP method of the request.
The Access-Control-Allow-Methods and Access-Control-Allow-Headers headers are consulted by the browser only during the CORS preflight. For the specific spec section which states the relevant requirements, see https://fetch.spec.whatwg.org/#cors-preflight-fetch. So you must include those in OPTIONS responses, but you can omit them from responses to other HTTP methods if you want.
The Access-Control-Allow-Credentials response header can be omitted from responses to OPTIONS requests, but it must be included in responses to other methods.
Could someone please provide an overview / walk-through of how headers are being passed by API Gateway on a CORS api call?
Your frontend code tells the browser it wants to send a request with a barerToken header.
Your browser says, OK, requests with a barerToken header require me to do a CORS preflight OPTIONS check to make sure the server allows requests with the barerToken header.
Your browser sends the server an OPTIONS request with no cookies, and no barerToken header included (because the OPTIONS check’s whole purpose is to see if it’s OK to include that header) and Access-Control-Request-Headers and Access-Control-Request-Method request headers (to tell the server what headers and method the server must give the OK for).
Your server sees the OPTIONS request and sends back a 200 OK response that includes the Access-Control-Allow-Methods and Access-Control-Allow-Headers response headers (to indicate what headers and methods it’s giving the OK for), and with the Access-Control-Allow-Origin header (to indicate if it is OK with requests from the origin the request came from).
Your browser evaluates the preflight OPTIONS response to determine whether it succeeds — based on the specific values in the Access-Control-Allow-Methods, Access-Control-Allow-Headers, and the Access-Control-Allow-Origin response headers.
If the preflight succeeds, the browser moves on to make the actual GET/POST/whatever request from your frontend JavaScript code — with cookies and the barerToken header included.
Your server sees the request, uses the cookies and barerToken value to do any authentication required — and then sends back whatever response you have configured, including the Access-Control-Allow-Origin (again) and Access-Control-Allow-Credentials response headers.

Access Denied from Cloudfront with Secure Cookies returns no CORS headers preventing reading error information from a XHR request

I'm using cloudfront secure cookies to keep some files private. When cookie auth succeeds and the origin is hit cloudfront returns the proper cors headers (Access-Control-Allow-Origin) from the origin but how do I make cloudfront return CORS headers during a 403/Access Denied? This validation is entirely in cloudfront before the request to the origin, but is there a setting to enable it? I want to be able to make a XHR request to cloudfront and know why the request failed. Since cloudfront doesn't return cors headers on a 403 most modern browsers will prevent reading any information on the request including the status code and its tough to determine why the request failed.
Thanks!
As you know, CloudFront doesn't spontaneously emit CORS headers -- they need to come from the origin server -- so in order to see CORS headers in the response, the request needs to be allowed by CloudFront... but, of course, it can't be allowed, because the condition you're trying to catch is 403 Forbidden.
So, what we need in order to allow your unauthorized responses to be CORS-friendly is an additional origin that can provide us with an alternate error response, and that origin needs to be CORS-aware. The solution seems to be something we can accomplish with a little help from CloudFront Custom Error Responses and an otherwise-empty S3 bucket, created for the purpose.
Custom error responses allow you to configure CloudFront to fetch the custom error response from another origin, rather than generating it internally. As part of that process, some headers from the original request are included in the upstream fetch, and the response headers from the error document are returned.
S3 makes a handy origin, since it has configurable CORS support.
Create a new, empty bucket.
Enable CORS for the bucket, and configure CORS with the appropriate parameters. The default configuration may be fine for this purpose.
Create a simple file that your CloudFront distribution will be using instead of its built in response for a 403. For test purposes, that can just be a text file that says "Access denied."
Upload the file to the bucket with whatever name you like, such as 403.txt. Select the option to make the object publicly-readable. Set metadata Cache-Control: no-cache, no-store, private, must-revalidate and Content-Type: text/plain (or text/html, depending on what exactly you put in the error file).
In CloudFront, create a new Origin. For the Origin Domain Name, select the bucket from the list of buckets.
Create a new Cache Behavior, matching path /403.txt (or whatever you named the file). Whitelist the Origin, Access-Control-Request-Headers, and Access-Control-Request-Method headers for forwarding. Set Restrict Viewer Access to No, because for this one path, we don't require signed credentials. Note that this path needs to be exactly the same as the filename in the bucket (except the leading slash, which isn't shown in the bucket but should be included, here).
In CloudFront Custom Error Responses, choose Create Custom Error Response. Select error code 403, set Error Caching Minimum TTL to 0, choose Customize Error Response Yes, set Response Page Path /403.txt and set HTTP Response code to 403.
Profit!
Test:
$ curl -v dzczcnnnnexample.cloudfront.net -H 'Origin: http://example.com'
* Rebuilt URL to: dzczcnnnnexample.cloudfront.net/
* Trying 203.0.113.208...
* Connected to dzczcnnnnexample.cloudfront.net (203.0.113.208) port 80 (#0)
> GET / HTTP/1.1
> Host: dzczcnnnnexample.cloudfront.net
> User-Agent: curl/7.47.0
> Accept: */*
> Origin: http://example.com
>
< HTTP/1.1 403 Forbidden
< Content-Type: text/plain
< Content-Length: 16
< Connection: keep-alive
< Date: Sun, 08 Apr 2018 14:01:25 GMT
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, HEAD
< Access-Control-Max-Age: 3000
< Last-Modified: Sun, 08 Apr 2018 13:29:19 GMT
< ETag: "fd9e8f7be7b65381c4acc272b6afc858"
< x-amz-server-side-encryption: AES256
< Cache-Control: private, no-cache, no-store, must-revalidate
< Accept-Ranges: bytes
< Server: AmazonS3
< Vary: Origin,Access-Control-Request-Headers,Access-Control-Request-Method
< X-Cache: Error from cloudfront
< Via: 1.1 1234567890a26beddeac6bfc77b2d348.cloudfront.net (CloudFront)
< X-Amz-Cf-Id: ExAmPlEIbQBtaqExamPLEQs4VwcxhvtU1YXBi47uUzUgami0Hj0MwQ==
<
Access denied.
* Connection #0 to host dzczcnnnnexample.cloudfront.net left intact
Here, Access denied. is what I put in the text file I created. You may want to get a little more creative, after confirming that this works for you, as it does for me. The content of this new file in S3 will always be returned whenever CloudFront throws a 403 error. Additionally, it will also be returned whenever your origin throws a 403, because custom error responses are designed to replace all errors with a given HTTP status code.
You note, above, that we see Access-Control-Allow-Origin: *. This is the default behavior of S3 CORS. If you provide explicit origins in the S3 CORS config, you get a response like this...
Access-Control-Allow-Origin: http://example.com
...but for GET requests, I assume this level of specificity would not be necessary and the wildcard would suffice. The scenario described here isn't setting CORS for the entire CloudFront distribution -- just for the error response.

CORS defeats AWS LAMBDA :(

I've been reading a lot on CORS/Lambda/AWS API Gateway configuration, including AWS's setup help:
http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
And this great article:
https://serverless.com/framework/docs/providers/aws/events/apigateway/
However, CORS is still defeating my API Gateway / Lambda function and I can't figure it out. Would love some help.
The meat of it seems to be that API Gateway proxies the request onto Lambda and you need to set "Access-Control-Allow-Origin": "*" headers in your Lambda function in addition to enabling CORS in the API Gateway, which I've done. I am getting a 200 response and can see the header being sent back correctly from my Lambda function. However, Chrome/FF still give me a CORS error. Any idea why?
Here are my request headers:
Host: myawshost.execute-api.us-west-2.amazonaws.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: application/json, text/javascript
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Origin: null
DNT: 1
Connection: keep-alive
And the 200 response headers:
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 74
Content-Type: application/json
Date: Fri, 23 Dec 2016 08:35:02 GMT
...
That all looks nice and successful right? But yet, I get no JSON back from Lambda and this error message in console:
"Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://myawshost.execute-api.us-west-2.amazonaws.com/prod/view?id=272. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). (unknown)"
But Access-Control-Allow-Origin is not missing! I can see it there in the response.
Any thoughts?
I've also spent quite some time researching this, and what made it work for me was returning the following in the lambda handler (Python):
return {
'statusCode': status_code,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': status_message if status_code == 200 else 'Failure'
}
Hello, i fixed that problem by setting cors and requested headers in
Api Gateway
first i set the headers i will want in my Method Request after
that i want to include them in the enable CORS menu
after that dont forget to deploy the api to public the new changes in
the api
I hope it will fix it
Regards