Let’s see how you set up CORS using a Lambda Proxy API and AWS SAM. Heads up! This guide will also help fix a CORS error that fails in the browser but works in CURL.
Table of Contents
About CORS
Cross-Origin Resource Sharing (CORS) is a way to tell browsers which domain, HTTP methods, and HTTP header(s) to trust. Therefore, this mechanism allows a server to indicate any origins (domain, scheme, or port) (and more) other than its own from which a browser should permit loading resources.
This header-based mechanism relies on pre-flight requests made by clients before actually sending the intended request off. The pre-flight request sends a request to the OPTIONS method of the API to achieve this.
When do you need CORS?
You can group CORS requests into two types: simple requests and non-simple requests.
A CORS request is simple if all the following are true:
- The API Only accepts GET, HEAD, and POST requests.
- The request includes the Origin header for the POST method request.
- The request payload content type is: text/plain, multipart/form-data, or application/x-www-form-urlencoded.
- The API resource does not contain custom headers.
- And lastly, any additional requirements are listed in the Mozilla CORS documentation.
All other CORS requests are non-simple requests and require your API to enable CORS support.
About AWS SAM
The Serverless Application Model (SAM) is a framework where you can build serverless applications. It provides shorthand syntax for defining functions, APIs, and more in one document using YAML – without having any setup or maintenance hassle!
Let’s Get Started!
First, you need to set up the CORS config in the globals section of your AWS SAM template.
Consequently, your globals section should look like this:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CORS via Globals
Globals:
Api:
Cors:
AllowOrigin: "'*'"
AllowHeaders: "*"
AllowMethods: "'*'"
Resources:
MyFunction:
...
Next, you need the CORS config to REST API resource.
This setting means your API section should look like this:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CORS via resource
Resources:
MyRestApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Cors:
AllowOrigin: "'*'"
AllowHeaders: "'*'"
AllowMethods: "'*'"
...
Finally, you must add the CORS config to the REST API response.
Therefore, your Lambda handler should respond with something like this:
{
"statusCode": status_code,
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "*",
},
"body": json_str,
}
Beware of The ApiKeyRequired Parameter!
In some cases, the ApiKeyRequired can cause problems for JavaScript frameworks. For example, setting this parameter in the “AWS::Serverless::Api” section enables the “x-api-key” header for preflight checks. However, the JavaScript framework may not always include the “x-api-key” in the pre-flight checks.
The example below sets the ApiKeyRequired parameter in the API Resource block:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CORS via resource
Resources:
MyRestApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true # sets for all methods
Cors:
AllowOrigin: "'*'"
AllowHeaders: "'*'"
AllowMethods: "'*'"
...
Example output in API Gateway:
You can avoid this error by setting ApiKeyRequired to false and then applying this setting manually on the “AWS::Serverless::Function” resource.
Consequently, your Function resource should look like this:
ApiPostCustomer:
Type: "AWS::Serverless::Function"
Properties:
...
Events:
apiPostCustomerEvent:
Type: "Api"
Properties:
...
Auth:
ApiKeyRequired: true
...
As a result, your API Gateway will look like this:
Best Practice Considerations
The examples mentioned above set the access-control-allow-origin, access-control-allow-headers, and access-control-allow-methods to “*” or any. However, it would be best never to allow “any” as this is a terrible security practice. Thus, you need to be more specific and avoid “any”.
How can you test the CORS configuration?
CURL is a great tool to test your CORS configuration:
curl --verbose --location --request <method> '<url>'
But, of course, you must change the method and the URL parameters accordingly.
For example, here you need to:
- Set the method to “OPTIONS” or the intended HTTP method like “GET”.
- URL is the URL of your REST API
You will, as a result, see an output like this:
> curl --verbose --location --request OPTIONS 'https://czr4jjjj.execute-api.us-east-1.amazonaws.com/v1/customer'
* Trying 3.236.158.2:443...
* TCP_NODELAY set
...
...
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
< date: Tue, 10 May 2022 01:01:31 GMT
< content-type: application/json
< content-length: 3
< x-amzn-requestid: 4c3aaa9d-5b98-48ad-8c98-d7202c0fce97
< access-control-allow-origin: *
< access-control-allow-headers: *
< x-amz-apigw-id: R4noxGd_oAMFoVA=
< access-control-allow-methods: *
< x-amzn-trace-id: Root=1-6279b96b-4df2413134950b6753c093f4
<
{}
* Connection #0 to host czr4jjjj.execute-api.us-east-1.amazonaws.com left intact
If the ApiKeyRequired parameter were set on the API resource, you would see a forbidden response:
...
< HTTP/2 403
< date: Mon, 09 May 2022 22:37:59 GMT
< content-type: application/json
< content-length: 23
< x-amzn-requestid: 24b15573-feb7-4c8f-b4d2-00d6ef7129c9
< x-amzn-errortype: ForbiddenException
< x-amz-apigw-id: R4SnJHlMoAMFwgw=
< x-amzn-trace-id: Root=1-627997c7-1dc99815469066d339ab5b67
<
* Connection #0 to host czr4jjss0e.execute-api.us-east-1.amazonaws.com left intact
{"message":"Forbidden"}
Wrapping Up
You have learned how to enable CORS when using AWS Lambda Proxy API and AWS SAM. In addition, you have learned how to enable the API Key required parameter for all methods except OPTIONS, which can cause a “forbidden” error. Therefore, you should now be able to access your API resources by CURL and the browser.
You May Also Be Interested In
Sources:
- https://stackoverflow.com/questions/38689350/for-what-reason-i-can-access-the-resources-by-curl-but-not-in-the-browser
- https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-controlling-access-to-apis-keys.html
- https://docs.amazonaws.cn/en_us/serverless-application-model/latest/developerguide/sam-property-api-corsconfiguration.html#sam-property-api-corsconfiguration–examples
- https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html
- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-test-cors.html
- https://cors.serverlessland.com/