Serverless AWS Access-Control-Allow-Headers in preflight response - amazon-web-services

For some reasom this error appears from nowhere:
has been blocked by CORS policy: Request header field x-company-id is not allowed by Access-Control-Allow-Headers in preflight response.
Here is my serverless.yml lambda function:
healthPlan:
handler: src/handlers/health-plan.healthPlanHandler
events:
- http:
path: /health-plan
method: get
cors:
origin: ${self:custom.allowed-origin}
allowCredentials: ${self:custom.allow-credentials}
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
- X-Company-Id
- x-company-id
authorizer:
authorizerId: ${cf:auth-service-${self:provider.stage}.ApiAuthorizer}
type: TOKEN
I'm new to serverless. Could someone give me advice or the answer for current situation? Where should I look?
Thanks a lot for help!

Firstly, are you sure you need x-company-id? I see you have also X-Company-Id, which I'm guessing might be the actual header AWS expects?
Likely x-company-id is being treated as a custom header. I had this same CORS issue with Serverless and AWS when trying to pass actual custom headers to my API.
My issue was resolved by adding a resources section to serverless.yml to include CORS headers in API Gateway-level error responses and including CORS headers in the normal responses of my lambda handler.
The instructions for performing these 2 steps I found here:
https://dev.to/leventov/enable-cors-with-custom-headers-for-an-aws-lambda-function-behind-api-gateway-in-serverless-framework-3702
Note that, instead of including each header explicitly in the access-control-allow-headers field, you can also just whitelist all headers with * if this is not a security concern.

Related

Cloudflare strips headers on custom domain for Cloudflare Pages

I have a React app (hosted on Cloudflare Pages) consuming a Flask API which I deployed on DigitalOcean App platform. I am using custom domains for both, app.example.com and api.example.com respectively.
When I try to use the app through the domain provided by Cloudflare Pages, my-app.pages.dev, I have no issues.
But when I try to use it through my custom domain app.example.com, I see that certain headers get stripped in the response to the preflight OPTIONS request. These are
access-control-allow-credentials: true
access-control-allow-headers: content-type
access-control-allow-methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
allow: POST, OPTIONS
vary: Origin
This causes issues with CORS, as displayed in the browser console:
Access to XMLHttpRequest at 'https://api.example.com/auth/login' from origin 'https://app.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Users can login without any issues on Cloudflare provided domain my-app.pages.dev, but whenever they try to login through the custom domain, they receive this error.
Another detail: the only difference between the preflight request in two cases is, the browser sets the following on app.example.com
origin: https://app.example.com
referer: https://app.example.com/login
sec-fetch-site: same-site
And the following on my-app.pages.dev
origin: https://my-app.pages.dev
referer: https://my-app.pages.dev/login
sec-fetch-site: cross-site
I am using Flask-CORS with support_credentials=True to handle CORS on the API, and axios with {withCredentials: true} to consume the API on the frontend.
Is this due to a Cloudflare policy that I'm not aware of? Does anyone have a clue?
I just solved this problem. It was due to the App Spec on DigitalOcean. I had CORS specific setting on the YAML file:
I changed
- cors:
allow_headers:
- '*'
allow_methods:
- GET
- OPTIONS
- POST
- PUT
- PATCH
- DELETE
allow_origins:
- prefix: https://app.example.com # <== I removed this line
- regex: https://*.example.com
- regex: http://*.example.com
to
- cors:
allow_headers:
- '*'
allow_methods:
- GET
- OPTIONS
- POST
- PUT
- PATCH
- DELETE
allow_origins:
- regex: https://*.example.com
- regex: http://*.example.com
For reference, this is the cURL command I used to debug the problem:
curl -I -XOPTIONS https://api.example.com \
-H 'origin: https://app.example.com' \
-H 'access-control-request-method: GET'
So it wasn't due to Cloudflare. Funny enough, DigitalOcean App Platform traffic goes through Cloudflare by default which added to my confusion.

API Gateway HTTP API CORS

I am using the new API Gateway HTTP which during the configuration enables you to add CORS. So I have set the Access-Control-Allow-Origin Header with the setting *.
However when I make a request using Postman I do not see that header and this i causing my VueJS Axios request to fail.
I previously used a Lambda Proxy Integration and did the following in my Lambda
"headers": {
"Access-Control-Allow-Origin": "*"
}
However the new HTTP API just does not seem to implement CORS. Maybe I am missing something simple.
--EDITS--
So I have continue to find an answer and came across a blog post from the guys at Serverless who set the following
It’ll ensure following headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers:
Content-Type, X-Amz-Date, Authorization, X-Api-Key, X-Amz-Security-Token, X-Amz-User-Agent
Access-Control-Allow-Methods:
OPTIONS, and all the methods defined in your routes (GET, POST, etc.)
I have tried these and redeployed and still only get the standard headers
Thanks
For anyone using HTTP API and the proxy route ANY /{proxy+}
You will need to explicitly define your route methods in order for CORS to work.
Wish this was more explicit in the AWS Docs for Configuring CORS for an HTTP API
Was on a 2 hour call with AWS Support and they looped in one of their senior HTTP API developers, who made this recommendation.
Hopefully this post can save some people some time and effort.
If you have a JWT authorizer and your route accepts ANY requests, your authorizer will reject the OPTIONS request as it doesn't contain an Authorization/Bearer token. To resolve this issue, you need to explicitly point your route to the HTTP request/method you need. E.g. POST
In that case, your authorizer will ignore the OPTIONS request without a JWT and proceed with the required request.
(This is answer just for AWS HTTP api gateway/AWS api gateway v2).
I have met the same problem and I asked the AWS support finally. The tricky thing is actually CORS had been already working after we configured it, but I just didn't get how to test it (it's different from AWS REST API ), when test we need to specify the Origin(should be same with your setting:Access-Control-Allow-Origin in cors, like: http://example.com) and the Request method.
For example:
curl -v -X GET https://www.xxx.yy/foo/bar/ -H 'Origin:http://example.com' -H 'Access-Control-Request-Method: GET'
Then it would return the CORS header you had set in response header:
access-control-allow-origin, access-control-allow-methods, access-control-allow-headers, etc.
I just hope this can save time for who met the similar problem.
By the way, I hope AWS can update this test way to offical Document(it's not very useful, but most of people must be saw it before find real answer):
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html
My issue was that I was sending this headers :
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Credentials
From the web request and it was messing with the Access-Control-Allow-Headers. I removed them and it's fine now.
To debug CORS issue with AWS HTTP API, try to put a * in each field of the CORS configuration in the AWS Console and reconfigure each field one by one.
So even though i was getting cors headers in my post call, browsers were still failing,,
My solution was
explicitly create an OPTIONS route with same path {proxy+}
attach same lambda integration to it
have the lambda function return early with success headers
if (method === 'OPTIONS') {
return {
headers: { 'Access-Control-Allow-Origin': '*' },
statusCode: 200
}
}
tldr; x-api-key
It took some searching and trial and error, but I got CORS working for API Gateway HTTP API. For the very basic GET request from a jQuery ajax call here is what I had to do:
AWS Console CORS settings, needed Access-Control-Allow-Headers: x-api-key
Then, in my ajax call, I had to send a dummy x-api-key (I did not configure one, so not sure why it wants it.):
$.ajax ({
url: 'https://xxxxxxx.execute-api.us-east-1.amazonaws.com/prod/stuff/',
crossDomain: true,
method: 'GET',
headers: {
"accept": "application/json",
"X-Api-Key":"blablablalla"
},
success:function(data){
doStuff(data);
},
error: function(){
alert('error');
}
})
You may need additional configuration depending on your situation, but the x-api-key was what I narrowed down as the oddest undocumented requirement.

AWS API Gateway supports CORS for OPTIONS only when using SAM (without Lambda proxy integration)

I'm using AWS Serverless for building a small site with around 15 Lambda functions.
My Cloudformation stack is completely built using SAM.
I'm NOT using Lambda proxy integration.
The Api section in the SAM yaml template config looks like this:
AppApi:
Type: AWS::Serverless::Api
Properties:
Cors:
AllowMethods: "'*'"
AllowHeaders: "'Content-Type'"
AllowOrigin: "'*'"
...........More Stuff..........
When I deploy this SAM yaml template, I see that my ApiGateway created the OPTIONS verb for all methods and when I shoot a request with the OPTIONS verb, I do see the CORS headers correctly.
The problem is that the other verbs (e.g. POST) don't add those headers to their response as the OPTIONS request did and when I run my api from the browser I get the cross origin policy error in my console.
So my current workaround was to add the CORS header using integrated responses to specific status codes, but I cannot and dont want to handle that for 15 methods and I want to support all response status codes (e.g. 4xx\5xx etc.).
My questions:
Am I doing anything wrong here or is this a SAM bug?
If this is a bug, is there any workaround other from adding the headers using integrated responses (or from my code)?
Is there a way I can add headers "globally" from an Api Gateway? Or support some kind of global integrated responses?
If you are using Lambda with Proxy Integrations, you need to specify the CORS Origin in your HTTP response.
For Lambda or HTTP proxy integrations, you can still set up the
required OPTIONS response headers in API Gateway. However, you must
rely on the back end to return the Access-Control-Allow-Origin headers
because the integration response is disabled for the proxy
integration.
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
All responses from Lambda need to have these headers and status code, but you could extract that to a shared library to reduce code duplication. Errors handled by API-G will have the headers added automatically.
You probably already have this, but the NodeJS pattern is like this:
var response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin" : "*"
},
body: JSON.stringify({
someReturnData
})
};
callback(null, response);
If you really don't want to do this then you can turn off Lambda-Proxy Integration, but that means that all request response payloads need to be handled in API-G instead of Lambda. IMO this provides much less flexibility and more configuration required to achieve the same results.
Here is an interesting comparison of the two approaches.

How to add Method Response in serverless.yml

I'm trying to add CORS header to Method Response header with serverless. Here's my config:
- http:
path: /myapi
method: GET
cors: true
My expectation is that CORS is enabled in GET method, but only OPTION method comes up. From my research, I stumbled upon this setting:
- http:
path: /myapi
method: GET
cors: true
"responseModels": {"application/json": "Empty"}
"statusCode": "200"
"responseParameters": {"method.response.header.Access-Control-Allow-Origin": true}
However nothing showed up and no error when I deployed serverless. My understanding is that a 200 response status code has to exist before CORS can be added here. When I create a new resource using UI console, a 200 status code is added automatically but serverless doesn't create it.
Any suggestion to achieve this without me creating a 200 status code manually?
This should work. I do this every day. I use Python, I set the CORS to true in serverless.yml and also must explicitly, manually, set the Access-Control-Allow-Origin to * in every response. Maybe that is not ideal, but it works for us.

How to restrict a Lambda function to respond only to specific origins?

I want to restrict my Lambda function (created with the Serverless Framework tool) to accept requests only from abc.com and def.com. It should reject all other requests. How can I do this? I tried setting access control origins like this:
cors: true
response:
headers:
Access-Control-Allow-Origin: "'beta.leafycode.com leafycode.com'"
and like this in the handler:
headers: {
"Access-Control-Allow-Origin" : "beta.leafycode.com leafycode.com"
},
but nothing worked. Any idea why?
The issue with your code is that Access-Control-Allow-Origin doesn't accept multiple domains.
From this answer:
Sounds like the recommended way to do it is to have your server read
the Origin header from the client, compare that to the list of domains
you'd like to allow, and if it matches, echo the value of the Origin
header back to the client as the Access-Control-Allow-Origin header in
the response.
So, when writing support to the OPTIONS verb, which is the verb where the browser will preflight a request to see if CORS is supported, you need to write your Lambda code to inspect the event object to see the domain of the client and dynamically set the corresponding Access-Control-Allow-Origin with the domain.
In your question, you have used a CORS configuration for two different types: Lambda and Lamba-Proxy. I recommend that you use the second option, so you will be able to set the domain dynamically.
headers: {
"Access-Control-Allow-Origin" : myDomainValue
},
See more about CORS configuration in the Serverless Framework here.