How to setup CORS for Lambda Proxy integration

How to solve CORS errors with API Gateway and Lambda proxy integration

5 mins
I have a lot of challenges when it comes to AWS, but I bet your pain points are entirely different than mine. I'd love to hear what keeps you up at night. It would be great to hear from you by filling out this form. Thanks in advance!

CORS with Lambda

The URL of an API behind API Gateway is in the form of https://<id>.execute-api.<region>.amazonaws.com, but your users are usually using a different URL to get your frontend. This can be a website hosted from an S3 bucket, a CloudFront distribution, or something entirely different. But since the domains are different, calling the API is a cross-origin request that requires CORS headers, most apparent from this error:

Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

In this example, I’ll use a website deployed to an S3 bucket. This is cross-origin as the bucket website URL is different than the API Gateway URL.

resource "aws_s3_bucket" "frontend_bucket" {
  website {
    index_document = "index.html"
  }
}

http://<bucket>.s3-website.<region>.amazonaws.comhttps://<id>.execute-api.<region>.amazonaws.comFrontendLambdaAPI GatewayuserProxy integrationDifferent origins

With Lambda proxy integration, the CORS headers are to be set on the Lambda-side instead of on the API Gateway. I’ll assume you’re using this setup in this article.

Credentials

Cross-origin requests can be made in two ways: with or without credentials. Credentials in this context are anything that makes the user identifiable, which usually means cookies. Without cookies sent, the backend gets an anonymous request. With credentials included, the server receives the cookies and can identify the logged-in user.

When a fetch is made, by default, it won’t include credentials in cross-origin requests:

fetch("URL") // no credentials
fetch("URL", {credentials: "include"}) // with credentials

Without credentials

If you don’t need to include cookies or other credentials, then set Access-Control-Allow-Origin: *:

bucket websiteAPI Gatewayother domainsfetchAccess-Control-Allow-Origin: *OKfetchAccess-Control-Allow-Origin: *OKfetch{credentials: "include"}Access-Control-Allow-Origin: *Exception

return {
	statusCode: 200,
	headers: {
		"Access-Control-Allow-Origin": "*",
	},
	body: ...,
};

This way the API can be called from anywhere but without login information. If you need to, you can still use query parameters to enforce authorization, just like signed URLs do. You can pass a JWT token this way or use any other means of credentials.

With credentials

What if you need to use cookies?

In this case, the server needs to include the Access-Control-Allow-Credentials: true header and also it must not use the Access-Control-Allow-Origin: *.

Because the wildcard is not allowed in this case, you need a whitelist of domains and need to verify the Origin header. This list should come from the environment as it might not be known build-time:

aws_s3_bucketwebsiteaws_lambda_functionenvironmentALLOWED_ORIGINSwebsite_endpoint

resource "aws_lambda_function" "lambda" {
	# ...
  environment {
    variables = {
      ALLOWED_ORIGINS = join(" ", ["http://${aws_s3_bucket.frontend_bucket.website_endpoint}"])
    }
  }
}

With the whitelist, requests made from allowed domains will get the CORS headers, but those from other domains won’t.

bucket websiteAPI Gatewayother domainsfetch{credentials: "include"}Access-Control-Allow-Origin:bucket websiteAccess-Control-Allow-Credentials:trueVary: OriginOKfetchException

Then to check the Origin header and set the CORS response headers only for whitelisted domains present in this environment variable:

const corsHeaders = (headers) => {
	const origin = headers.origin || headers.Origin;

	if (process.env.ALLOWED_ORIGINS.split(" ").includes(origin)) {
		return {
			"Access-Control-Allow-Origin": origin,
			"Access-Control-Allow-Credentials": "true",
			"Vary": "Origin", // for proxies
		};
	}
};

// ...

return {
	statusCode: 200,
	headers: {
		...corsHeaders(event.headers),
	},
}

Conclusion

Using multiple domains always comes with cross-origin errors and it’s easy to get lost in all the nuances and edge cases of the relevant specifications. I hope this guide will help you get an overview of CORS and solve CORS-related errors with Lambda and API Gateway.

Download our ebook on AWS account security basics

Learn 5 simple steps to avoid the rookie mistakes.

  1. Why the root account is bad for security
  2. Use multiple users
  3. Secure accounts with multi-factor authentication
  4. Security logging with CloudTrail
  5. Billing alerts as an early warning system

Download the free guide here:

19 November 2019