How to use API Gateway with CloudFront
The unique challenges when integrating API Gateway with CloudFront
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
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
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
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 {
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 {
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 {
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/
.
Update: With CloudFront Functions it is now possible to solve this without the complicated setup of Lambda@Edge.
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.