How to add CORS header to AWS API Gateway response with lambda proxy integration activate - amazon-web-services

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

Related

Spring Cloud Function AWS Lambda with proxy integration on APIGW : CORS issue

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.

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({})
}

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.

How can an aws lambda know what endpoint called it from API Gateway?

If two different endpoints use one lambda, how can the lambda know about the parts of the URL path?
How can one lambda know it was called from /zips vs /zip?zip_code=02140 ?
I can use event["queryStringParameters"]['zip_Code'] to get the URLs query string - /zip?zip_code=02140 - from within the lambda,
but how can I know if I am called from the /zips endpoint?
I tried using event["pathStringParameters"]['zips'] which I created a test event for but that didn't work, not recognized.
I can use one lambda per specific resource but I'd like to also know other approaches and how those that use the same endpoint can have their path revealed.
If I am following what you're asking for, namely that you have one Lambda function servicing two API Gateway endpoints, then I think you have two options:
Use the path parameter
Set a custom header and check that in headers
From the AWS documentation:
In Lambda proxy integration, API Gateway maps the entire client request to the input event parameter of the backend Lambda function as follows:
So given this HTTP request:
POST /testStage/hello/world?name=me HTTP/1.1
Host: gy415nuibc.execute-api.us-east-1.amazonaws.com
Content-Type: application/json
headerName: headerValue
{
"a": 1
}
You'll have available:
"message": "Hello me!",
"input": {
"resource": "/{proxy+}",
"path": "/hello/world",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
...
Here both path and headers will serve your need.
Personally, I would recommend setting a custom header. That way, no matter if your API routing changes, your Lambda will still pick it up.
You can get the path that invoked your Lambda in the event object, under event["requestContext"]["path"]
You can see more details on what the event object contains in the documentation for using AWS Lambda with API Gateway

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