How to enable CORS on API Gateway with Lambda proxy integration?

Andreas Wittig – 21 Oct 2020

When building single-page applications (SPA), you will sooner or later stumble upon Cross-Origin Resource Sharing (CORS). In short, a browser does only allow requests to the same same origin (domain, protocol and port), that was used for the initial request by default.

How to enable CORS on API Gateway with Lambda proxy integration?

However, a typical Serverless application uses CloudFront and S3 to deliver the static files like .html, .css, and .js and an API Gateway acting as the front door for the backend. In that case, the hostname to access CloudFront - for example, myapp.com - is different than the hostname to access the API Gateway - for example, api.myapp.com. Therefore, a browser would block requests to api.myapp.com. To allow the SPA to access the API Gateway, you need to implement CORS on the backend. This article explains how to do so when using the API Gateway with Lambda proxy integration.

CORS required for Serverless single-page applications with CloudFront and API Gateway

The proxy integration is an easy way to configure the API Gateway. It starts with the gateway forwarding all parts of an HTTP request to the Lambda function. Next, the Lambda function returns all details of an HTTP response. No complicated configuration and data mapping needed on the API Gateway.

Three steps are necessary to enable CORS for the backend when using the Lambda proxy integration:

  1. Implement adding CORS headers with the Lambda function
  2. Add static response for OPTIONS requests
  3. Add CORS headers to server-side errors

You will learn more about those three steps in the following.

Implement adding CORS headers with the Lambda function

When configuring the proxy integration on the API Gateway, the Lambda function needs to return a response in a specific format. The following code snippet shows how to add the necessary CORS header Access-Control-Allow-Origin.

exports.handler = async (event) => {
let data = {};
let res = {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' // replace with hostname of frontend (CloudFront)
},
body: JSON.stringify(data)
};
return res;
};

Remember to add the Access-Control-Allow-Origin to all Lambda functions.

Add static response for OPTIONS requests

Additionally, the browser sends a so called CORS preflight requests (HTTP method OPTIONS) to each API resource to check for CORS configuration. Therefore, you need to make sure that the API Gateway answers OPTIONS requests for all resources that should be accessed by the SPA. To simplify the configuration, creating a proxy resource allows you to add an OPTIONS method to all resources at once. The following Terraform configuration snippet shows how to create a proxy resource to answer the OPTIONS requests for all API resources.

resource "aws_api_gateway_rest_api" "backend" {
name = "myapp"
}

resource "aws_api_gateway_resource" "cors" {
rest_api_id = aws_api_gateway_rest_api.backend.id
parent_id = aws_api_gateway_rest_api.backend.root_resource_id
path_part = "{cors+}"
}

resource "aws_api_gateway_method" "cors" {
rest_api_id = aws_api_gateway_rest_api.backend.id
resource_id = aws_api_gateway_resource.cors.id
http_method = "OPTIONS"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "cors" {
rest_api_id = aws_api_gateway_rest_api.backend.id
resource_id = aws_api_gateway_resource.cors.id
http_method = aws_api_gateway_method.cors.http_method
type = "MOCK"
}

resource "aws_api_gateway_method_response" "cors" {
depends_on = [aws_api_gateway_method.cors]
rest_api_id = aws_api_gateway_rest_api.backend.id
resource_id = aws_api_gateway_resource.cors.id
http_method = aws_api_gateway_method.cors.http_method
status_code = 200
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = true,
"method.response.header.Access-Control-Allow-Methods" = true,
"method.response.header.Access-Control-Allow-Headers" = true
}
response_models = {
"application/json" = "Empty"
}
}

resource "aws_api_gateway_integration_response" "cors" {
depends_on = [aws_api_gateway_integration.cors, aws_api_gateway_method_response.cors]
rest_api_id = aws_api_gateway_rest_api.backend.id
resource_id = aws_api_gateway_resource.cors.id
http_method = aws_api_gateway_method.cors.http_method
status_code = 200
response_parameters = {
"method.response.header.Access-Control-Allow-Origin" = "'*'", # replace with hostname of frontend (CloudFront)
"method.response.header.Access-Control-Allow-Headers" = "'Content-Type'",
"method.response.header.Access-Control-Allow-Methods" = "'GET, POST'" # remove or add HTTP methods as needed
}
}

That’s it. The API Gateway will answer all CORS related OPTIONS requests now.

Add CORS headers to server-side errors

Finally, you need to make sure that the API Gateway is also adding the CORS headers in case of server-side errors. Why is that important? For example, when the API Gateway fails to invoke your Lambda function, it will return a server-side error. In that case, the Lambda function cannot add the necessary Access-Control-Allow-Origin header to the response. Therefore, you need to configure the API Gateway to add the CORS header for server-side errors, as illustrated in the following Terraform configuration snippet.

resource "aws_api_gateway_gateway_response" "response_4xx" {
rest_api_id = aws_api_gateway_rest_api.backend.id
response_type = "DEFAULT_4XX"

response_templates = {
"application/json" = "{'message':$context.error.messageString}"
}

response_parameters = {
"gatewayresponse.header.Access-Control-Allow-Origin" = "'*'" # replace with hostname of frontend (CloudFront)
}
}

resource "aws_api_gateway_gateway_response" "response_5xx" {
rest_api_id = aws_api_gateway_rest_api.backend.id
response_type = "DEFAULT_5XX"

response_templates = {
"application/json" = "{'message':$context.error.messageString}"
}

response_parameters = {
"gatewayresponse.header.Access-Control-Allow-Origin" = "'*'" # replace with hostname of frontend (CloudFront)
}
}

That’s it. You have successfully enabled CORS for your backend.

Summary

When using the Lambda proxy integration, the following steps are necessary to enable CORS on the backend of your Serverless application.

  1. Implement adding CORS headers with the Lambda function
  2. Add static response for OPTIONS requests
  3. Add CORS headers to server-side errors

By the way, you could also avoid CORS entirely by running the API Gateway behind the same domain as your SPA. For example, by configuring CloudFront to route requests to myapp.com/api/* to a regional API Gateway while routing everything else to an S3 bucket.

Andreas Wittig

Andreas Wittig

I’ve been building on AWS since 2012 together with my brother Michael. We are sharing our insights into all things AWS on cloudonaut and have written the book AWS in Action. Besides that, we’re currently working on bucketAV,HyperEnv for GitHub Actions, and marbot.

Here are the contact options for feedback and questions.