📣 Limited offer: subscribe to cloudonaut plus, get a t-shirt for free

📣 Limited offer: free cloudonaut t-shirt

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.

Special offer: cloudonaut t-shirt

Do you love our blog posts and podcast episodes? Unlock our weekly videos and online events by subscribing to cloudonaut plus.

Special offer: Join cloudonaut plus before November 30th, and we will send you a cloudonaut t-shirt for free.

Subscribe now!

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’m an independent consultant, technical writer, and programming founder. All these activities have to do with AWS. I’m writing this blog and all other projects together with my brother Michael.

In 2009, we joined the same company as software developers. Three years later, we were looking for a way to deploy our software—an online banking platform—in an agile way. We got excited about the possibilities in the cloud and the DevOps movement. It’s no wonder we ended up migrating the whole infrastructure of Tullius Walden Bank to AWS. This was a first in the finance industry, at least in Germany! Since 2015, we have accelerated the cloud journeys of startups, mid-sized companies, and enterprises. We have penned books like Amazon Web Services in Action and Rapid Docker on AWS, we regularly update our blog, and we are contributing to the Open Source community. Besides running a 2-headed consultancy, we are entrepreneurs building Software-as-a-Service products.

We are available for projects.

You can contact me via Email, Twitter, and LinkedIn.

Briefcase icon
Hire me