Spring Cloud Function AWS Lambda with proxy integration on APIGW : CORS issue - amazon-web-services

We have created our AWS Lambda function using Spring Cloud function. This function returns APIGatewayProxyResponseEvent response. Sample below
{
"statusCode": 200,
"headers": {
"Access-Control-Expose-Headers": "Access-Control-Allow-Methods,Access-Control-Allow-Origin",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET",
"Access-Control-Max-Age": "200",
"Access-Control-Allow-Headers": "Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers",
"Content-Type": "application/json"
},
"multiValueHeaders": null,
"body": "response Data json Body",
"isBase64Encoded": false
}
APIGW uses Lambda proxy integration , and hence there is no option for response mappings.
We have enabled CORS by using Actions on the console. This automatically adds the OPTIONS method where we have configured the 200 response with below headers
Access-Control-Max-Age : '200'
Access-Control-Allow-Headers : 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Expose-Headers : 'Access-Control-Allow-Methods,Access-Control-Allow-Origin'
Access-Control-Allow-Origin : '*'
Access-Control-Allow-Methods : 'GET,OPTIONS'
The above steps are in sync with the AWS documentation AWS - How to CORS Lambda proxy
We deployed the API in a stage and are able to access it via Postman. On accessing from our web-application, which is currently on localhost we get CORS error.
In Network tab its visible that the preflight request (OPTIONS) returns 200 OK and the required CORS headers. However the actual GET call still fails , saying "CORS Error".
The issue is that APIGW is not copying the headers returned in the APIGatewayProxyResponseEvent object to final APIGW Response headers
Is this a known issue or am I missing something
Edit
Screen shot of APIGW lambda proxy
Screen shot from APIGW response (Testing from console)
Network tab in browser developer options showing preflight request successful
Edit2
Adding Console output

Edit 1
On checking the spring cloud milestone release, This issue has been addressed starting 3.2.0-M1 . (Currently available release is 3.1.5). Once this is released my previous approach of sending APIGatewayProxyResponseEvent as output will work just fine.
#Oleg Zhurakousky can confirm
Original Answer below : (almost a work around)
Got help from AWS support and understood that the response being returned from the Spring Cloud function was being modified. This in turn resulted in all the headers being encapsulated as part of Lambda response body.
My previous implementation of the function was
#Bean
public Function<APIGatewayProxyRequestEvent,APIGatewayProxyResponseEvent> testFunc2(){
return event -> {
System.out.println(event);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
Map<String,String> hdr = new HashMap<>();
hdr.put("Access-Control-Allow-Origin","*");
response.setHeaders(hdr);
response.setStatusCode(HttpStatus.OK.value());
response.setBody("Hello World!");
return response;
};
}
I had to change it to below to make sure the headers are treated as http headers and not a part of the lambda response body
#Bean
public Function<APIGatewayProxyRequestEvent, Message<APIGatewayProxyResponseEvent>> testFunc3(){
return event -> {
System.out.println(event);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
Map<String,Object> hdr = new HashMap<>();
hdr.put("Access-Control-Allow-Origin","*");
response.setStatusCode(HttpStatus.OK.value());
response.setBody("Hello World!");
Message<APIGatewayProxyResponseEvent> finalResponse = new GenericMessage<APIGatewayProxyResponseEvent>(response,hdr);
System.out.println("Response prepared " +response);
System.out.println("Final Response being returned " +finalResponse);
return finalResponse;
};
}
The actual entry point into Spring cloud function is org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest
Here while preparing response spring takes the returned value from the function as Message (org.springframework.messaging) Payload.
Hence In order to set http headers we need to return a Message<APIGatewayProxyResponseEvent> instead of APIGatewayProxyResponseEvent. Here we explicitly set our http headers in Message headers.

Related

How to set the api version being called in API Gateway when integrating with AWS Service CloudWatch?

I get the following error message when calling actions for CloudWatch in API Gateway.
"Error": {
"Code": "InvalidAction",
"Message": "Could not find operation DescribeAlarms for version 2009-05-15",
"Type": "Sender"
}
I've been using DescribeAlarms for testing. My setup is as follows.
Integration Type = AWS Service
AWS Service = CloudWatch
HTTP method = POST
Action = DescribeAlarms
The error references the API Version 2009-05-15, which only has ListMetrics and GetMetricStatistics according to it's documentation on page 54. ListMetrics does indeed work as expected with my setup.
The current version is 2010-08-01 but I don't see anyway to reference that in API Gateway. In an example of a POST request in the documentation it shows a header labeled x-amz-target with a value of GraniteServiceVersion20100801.API_Name.
My interpretation is I can put Name = x-amz-target and value 'GraniteServiceVersion20100801.DescribeAlarms' in my http header for the Integration Request in API Gateway.
This doesn't change the response and gives the same error message.
I also used the --debug in CLI when calling describe-alarms, and in the body it shows...
"body": {
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
So I also set http headers to include Content-Type with a value of 'application/x-amz-json-1.1' and then put in
{
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
but nothing changed with that either.
Any help or guidance would be greatly appreciated.
Under Method Integration -> URL Query String Parameters
I added Version as the Name and '2010-08-01' under Mapped From.
All actions are now working as expected.
I'm trying to PutMetrics directly from Api Gateway -> Cloudwatch using PutMetricData, Version in the query string params didn't work for me.
These 3 HTTP headers in the Integration Request solved it for me:
Content-Type 'application/json'
X-Amz-Target 'GraniteServiceVersion20100801.PutMetricData'
Content-Encoding 'amz-1.0'

Call to API Gateway returning CORS error despite headers being set

I have an API Gateway (of type HTTP) that integrates with a Lambda function. I am trying to call that function from localhost as follows:
const jwt = getAuthToken();
const formBody = new FormData();
formBody.set('user', 'test');
const res = await fetch('https://example.execute-api.eu-west-1.amazonaws.com/default/GetShareValue', {
method: 'POST',
body: formBody,
headers: {
'Authorization': jwt
}
});
However, I get this error message:
Access to fetch at
'https://example.execute-api.eu-west-1.amazonaws.com/default/GetShareValue'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
I understand that incorrect CORS settings will prevent the browser from displaying what the Lambda function returns. However, my API Gateway has the CORS settings specified:
According to this thread, setting the headers in the Lambda function's response is important. I have set them to be identical. Here's my Lambda function:
exports.handler = async (event, context) => {
let body;
let statusCode = '200';
const headers = {
'Content-Type': 'application/json',
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"X-Requested-With": "*"
};
try {
body = "Success!!";
} catch (err) {
statusCode = '400';
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
I can call this endpoint in Postman - which doesn't care about CORS - so I know it's working. I am starting to think that this is a bug within AWS itself.
Answering my own question for the benefit of #peter n.
In the end I abandoned the HTTP API Gateway. I believe there is a bug in AWS as the exact same Lambda function worked when I triggered it via a REST API. Here's what I did.
Create a new API Gateway
Select the type of the API Gateway as REST API. I previously had selected HTTP. The REST API gives us more control; the HTTP API is simple (and 70% cheaper)
On the Resources page, click Actions > Create Resource
Create the resource; if you see any checkbox mentioning CORS then check it. I can't remember if this occurs at this stage.
Then click Actions > Create method
Select any method (like POST, GET etc.) other than "Any". This is important - if you select "Any" there are further issues with CORS. Why? Who knows, AWS is a user-unfriendly platform.
Once you've configured the method and got it firing at your Lambda function (or whatever you're integrating with) test it with something like Postman, which doesn't care about CORS.
Finally, to get CORS working click Actions > Enable CORS
I entered the following settings:
Methods: POST
Access-Control-Allow-Methods: OPTIONS, POST
Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'
Access-Control-Allow-Origin: '*'
Then click "Enable CORS and replace existing CORS headers".
You're not done yet. This still won't solve the issue if you're targetting a Lambda function. You need to add the CORS headers to the actual response of your Lambda.
Here's the full code for my Lambda:
exports.handler = async (event, context) => {
let body = JSON.stringify('Success!');
let statusCode = '200';
const headers = {
'Content-Type': 'application/json',
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"X-Requested-With": "*"
};
return {
statusCode,
body,
headers,
};
};
I am not sure if it matters that the CORS settings you return in the Lambda are the same CORS settings that you applied in the AWS Console.
This is a real headache and, unless I am doing something wrong, AWS have implemented this very badly.
Update: After having further issues with this I thought I'd update my answer to state that the request you make also seems to impact on CORS. I was making changes to my API and the CORS error started happening again; eventually, the only solution was to remove some of the headers in the request I was sending to AWS.
This caused CORS errors:
const res = await fetch('https://123.execute-api.eu-west-1.amazonaws.com/test', {
method: 'POST',
headers: {
'Authorization': jwt,
"Access-Control-Allow-Origin": "*" // This had to be removed to fix the CORS error
}
});
As you can see, I was specifying Access-Control-Allow-Origin in the outbound request to my API. This was always there and never caused issues, but seemingly now I can comment and uncomment that line in the request and get/not-get the CORS error.
Update #2: A further update to save anyone who may be banging their head against the wall for this.. after adding new sources and methods to an existing API Gateway, be sure to click Actions > Deploy API so your changes are actually live.
AWS really need to clean up the whole UI for API Gateway and Lambda. It is not clear at all that you have unpublished changes or that publishing is even required; the UI makes it seem like any changes are instantly live.
The main problem is in the answer provided by the Options method. For some reason, the mock response, at least in my case, does not work correctly, so I have linked it with a lambda whose sole purpose is to respond with a 200 code and returning all the necessary headers.
Therefore, what you have to do is, inside the options method of the resource with CORS enabled, change the integration request to Lambda.
Once this is done, we must develop the lambda so that it returns a 200 as a response and accepts all the necessary headers:
{
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"X-Requested-With": "*"
},
'body': json.dumps({})
}

Can I setup a custom response header to all my lambda functions?

I'm using lambda with CORS, and I currently have to setup manually for every response in every lambda function the following header:
const response = {
headers: {
"Access-Control-Allow-Origin" : "*", // Required for CORS support to work
"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
},
statusCode: 200,
body: JSON.stringify(params.Item),
};
Is there a way to setup the headers as a default response for all my lambdas? Perhaps in API Gateway or somewhere else so I don't have to manually add it for every single response?
You should set up CORS handing at the API gateway instead. That way you don't have to pay for your lambda to handle CORS OPTION requests.
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
API gateway can help achieve it, especially when we have mostly static response headers to add.
(1) go to resources > 'method response'... add your response headers for the 200 (OK) HTTP status (first image)
(2) go to resources > 'integration response'... add mappings to your values (second image)
for dynamic data in the response headers, lambda works best

How to add CORS header to AWS API Gateway response with lambda proxy integration activate

I use lambda as backend for AWS API Gateway with lambda proxy integration and want to add CORS into response header.
According to documentation:
http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
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.
How can I program it in my lambda function with Python.
To create OPTIONS method you can enable it from the Gateway
Navigate to your Gateaway, Select Resources from left side
Select endpoint, on top there will a button "Action", there you will need to select "Enable CORS", save the settings.
Deploy the Gateway.
It will create a method OPTIONS on the resource(endpoint)
for GET/POST other HTTP Verbs you will need to manage it from your code, in case of python
return {
'statusCode': "200",
'body': json.dumps({"test" : "123"}),
'headers': {
"Content-Type" : "application/json",
"Access-Control-Allow-Origin" : "*",
"Allow" : "GET, OPTIONS, POST",
"Access-Control-Allow-Methods" : "GET, OPTIONS, POST",
"Access-Control-Allow-Headers" : "*"
}
}
for other unhandled cases like IntegrationTimeout (504) or Error in your code (502), you can configure default response headers at API Gateway Level. refer Default Response Headers: AWS API Gateway w/ Proxy Integration
you need to add a method "options" to your api gateway and using a proxy lambda... return
result.headers = { "Access-Control-Allow-Origin": "domain.com" }
so when the browser will first call options to your server it will return the CORS headers.
the thing is that, by default your lambda method will be called for "any" method, so you need to change the default one to get,post or whatever you need
note: you could also use the same method, like any or options,get,post and if it is a options call, only return status 200 and the cors header. it depends if you are using or not an auth method for get,post,etc
there is an option in Lambda console "Enable CORS" if you are just using lambda with nothing strange

Setting http response header from AWS lambda

My API Gateway/Lambda setup returns an HTTP response header:
Lambda uses callback function to return the value as part of a JSON
and the Integration Response maps it into a HTTP header (using integration.response.body)
With this solution, the values are sent back both in the body and the header.
How can I map headers from the Lambda response without duplicating the values in the response body?
If you have Lambda proxy integration enabled, you can set the response headers as part of Lambda output and API Gateway will return them as part of the HTTP response to the client.
Node.js example:
callback(null, {
"isBase64Encoded": false, // Set to `true` for binary support.
"statusCode": 200,
"headers": {
"header1Name": "header1Value",
"header2Name": "header2Value",
},
"body": "...",
});
where headers can be null or unspecified if no extra response headers are to be returned.
See Output Format of a Lambda Function for Proxy Integration.
and, if you DON'T have Lamba proxy integration enabled, you can add (and map) the response headers in the amazon API gateway console:
go to resources -> method execution -> method response -> add 'Access-Control-Allow-Origin' (or whatever) header for http status 200. Then go back to method execution -> integration response -> http status 200 -> set header mapping for 'Access-Control-Allow-Origin' to '*' (or whatever).
Solved this error...: "No 'Access-Control-Allow-Origin' header is present on the requested resource"
Since the question states that custom mappings are being used (using integration.response.body), it means Lambda Proxy Integrations are NOT being used. So, the solution, in this case, is to map the headers the way you are already doing.
To remove the headers duplication from the body part, use mapping template in the integration response and ignore headers in the mapping. I think you might be using pass through responses, that's why you are seeing duplicate headers.
See more documentation here: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html