How to use API Gateway with CloudFront

The unique challenges when integrating API Gateway with CloudFront

6 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!

Background

CloudFront is a great tool for bringing all the different parts of your application under one domain. It does it by allowing different origins (backends) to be defined and then path patterns can be defined that routes to different origins.

But use it with API Gateway and you’ll see some unique problems.

The basic case

Cache BehaviorsOrigins/api/*API Gatewaydomain_nameorigin_path = '/stage'CloudFrontapigw

When you have an API Gateway and a CloudFront Distribution, you need to define an origin first:

origin {
	domain_name = replace(aws_api_gateway_deployment.deployment.invoke_url, "/^https?://([^/]*).*/", "$1")
	origin_id   = "apigw"
	origin_path = "/stage"

	custom_origin_config {
		http_port              = 80
		https_port             = 443
		origin_protocol_policy = "https-only"
		origin_ssl_protocols   = ["TLSv1.2"]
	}
}

Then a cache behavior that targets that origin under a path:

ordered_cache_behavior {
	path_pattern     = "/api/*"
	allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
	cached_methods   = ["GET", "HEAD"]
	target_origin_id = "apigw"

	default_ttl = 0
	min_ttl     = 0
	max_ttl     = 0

	forwarded_values {
		query_string = true
		cookies {
			forward = "all"
		}
	}

	viewer_protocol_policy = "redirect-to-https"
}

If you then visit the <cloudfront domain>/api/ URL it will be served by the API Gateway.

Caching

APIs are usually not cacheable so it’s a sensible default to disable proxy caching on the CloudFront side. This can be done by specifying all cache TTLs as 0:

ordered_cache_behavior {
	# ...

	default_ttl = 0
	min_ttl     = 0
	max_ttl     = 0
}

Cookies and query parameters

The same goes for cookies and query parameters. Usually, you want everything to be available for your API so it’s best to forward everything:

ordered_cache_behavior {
	# ...

	forwarded_values {
		query_string = true
		cookies {
			forward = "all"
		}
	}
}

Domain name

invoke_urlpathdomainscheme/<stageName><restApiId>.execute-api.<region>.amazonaws.comhttps://

This is where things get interesting. The origin’s domain_name attribute requires only a domain, but the invoke_url defines the whole URL: https://<restApiId>.execute-api.<region>.amazonaws.com/<stageName>.

To extract the domain, you can use the replace function with a regular expression:

origin {
	domain_name = replace(aws_api_gateway_deployment.deployment.invoke_url, "/^https?://([^/]*).*/", "$1")

	# ...
}

Origin request URL

When CloudFront constructs the URL for the backend, you can specify three parts:

  • the domain_name
  • the origin_path
  • and the path_pattern at the cache behavior

origin URLpathorigin_pathdomainclient URLpathdomain/api/users/stage<restApiId>.execute-api.<region>.amazonaws.com/api/users<distribution>.cloudfront.net

CloudFront constructs the URL to the origin by replacing the distribution URL with the domain_name+origin_path, then it appends the path. In the above example if the client opened <distribution>.cloudfront.net/api/users, then the final URL is <restApiId>.execute-api.<region>.amazonaws.com/stage/api/users.

Depending on the path pattern and the API Gateway stage name, there are 3 cases.

API Gateway is on /

This is when you define the cache behavior as the default. In this case, you need to add the stage name as the origin_path:

origin URLpathorigin_pathdomainclient URLpathdomain/users/stage<restApiId>.execute-api.<region>.amazonaws.com/users<distribution>.cloudfront.net

origin {
	domain_name = replace(aws_api_gateway_deployment.deployment.invoke_url, "/^https?://([^/]*).*/", "$1")
	origin_id   = "apigw"
	origin_path = "/stage"

	# ...
}
default_cache_behavior {
	target_origin_id = "apigw"

	# ...
}

This is a simple case, as each path will be translated directly to under the stage. In the case of a Lambda function, event.path will be the full path.

The stage name is the same as the path pattern

In this case, the path_pattern is also the API Gateway stage name. This way, there is no need for origin_path as every forwarded path starts with the stage:

origin URLpathdomainclient URLpathdomain/stage/users<restApiId>.execute-api.<region>.amazonaws.com/stage/users<distribution>.cloudfront.net

origin {
	domain_name = replace(aws_api_gateway_deployment.deployment.invoke_url, "/^https?://([^/]*).*/", "$1")
	origin_id   = "apigw"

	# ...
}
ordered_cache_behavior {
	path_pattern     = "/stage/*"
	target_origin_id = "apigw"

	# ...
}

The stage name is different than the path pattern

In this case, you need to set the origin_path to the stage name:

origin URLpathorigin_pathdomainclient URLpathdomain/api/users/stage<restApiId>.execute-api.<region>.amazonaws.com/api/users<distribution>.cloudfront.net

origin {
	domain_name = replace(aws_api_gateway_deployment.deployment.invoke_url, "/^https?://([^/]*).*/", "$1")
	origin_id   = "apigw"
	origin_path = "stage"

	# ...
}
ordered_cache_behavior {
	path_pattern     = "/api/*"
	target_origin_id = "apigw"

	# ...
}

This way both the stage name and the path_pattern will be appended to the final URL.

As a result, there is no way to remove the /api/ part of the URL sent to the origin without relying on Lambda@Edge. This can be a problem when your backend assumes all requests will come to / instead of /path_pattern/.

Conclusion

Path translation can be a source of confusion when you try to integrate API Gateway with CloudFront. I hope this overview will help you with debugging.

Did you know we have a free guide on AWS security basics?

Do you have an AWS account? Of course you do, you've just read an article about AWS. But do you know how to secure it?

As a certified security specialist I'm familiar with most of the security controls AWS offers. I've compiled this guide so you don't have to take a month off to learn all that.

5 quick and easy steps to avoid the rookie mistakes and reduce the risk of costly events down the road.

Download the free guide here:

24 September 2019