So long API Gateway, and thanks for all the routes
For nearly a decade I’ve been building APIs with API Gateway and Lambda. I was even fortunate enough to be part of the team that launched API Gateway back in 2015! It’s served me well and I’m sure I’ll use it again on future projects that require some of its more advanced features.
However, most of the time I just need a way to invoke a Lambda Function over HTTP with a custom domain name, and this is exactly what CloudFront + Lambda Function URL (CLFURL) enables.
Why change?
Cost
Since launch, one of the biggest criticisms of API Gateway has been its pricing. One would expect that Lambda (the thing that’s doing the heavy lifting) would make up the majority of the cost, and not the thing that’s simply connecting it to a client. In reality, API Gateway (even the newer, cheaper HTTP API variant) often exceeds Lambda costs by more than 2x. Under similar conditions, CloudFront costs ~10% of what Lambda does.
The one area where API Gateway has a cost advantage is on unauthorized requests. When using its Authorizer feature, API Gateway eats those costs for you. With CLFURL you would pay both the CloudFront and Lambda costs for unauthorized requests. This is mostly only a concern when it comes to DDOS (or Denial of Wallet) attacks. Fortunately, AWS Shield Standard (a free DDOS protection service included with CloudFront) should mitigate those attacks.
Max timeout
API Gateway has a max timeout of 29 seconds, which is more than enough for most REST API needs. However, with AI becoming a core feature of many products, those extra 31 seconds afforded by CloudFront (a total of 1 minute) can be crucial.
In fact, this is the main reason I started investigating this option in the first place! Code Genie uses AI to generate data models based on the description of your app. There is currently a 500 character limit on this description to mitigate the chance of it taking longer than 29 seconds. By moving to CLFURL, Code Genie will be able to handle significantly more complex requests, and enable other AI features in the future.
Performance?
In my tests comparing the performance of API Gateway to CloudFront, I found both approaches yield similar results. Here are the highlights:
- CloudFront adds a shocking +300ms latency for cross-region (other-side-of-the-world) requests when not under load. These findings are why I didn’t switch to CLFURL earlier this year. I recently decided to test the performance again, hoping that AWS had resolved the issue. They hadn’t. However, after diving deeper I discovered that this latency is akin to a cold start. When under load, this extra latency is a rare occurrence.
- Under load, CloudFront offers marginally better performance than API Gateway HTTP API. ~4% faster on average. These tests were only run against the cheaper, faster HTTP API option. If you’re using the original REST API option, you might see a more significant performance difference around the 20% mark (along with 3x the cost).
You can try it out yourself at https://production.d1gtqqp4ixg5qm.amplifyapp.com/public-api (no guarantees on how long this website will be live) or deploy your own version by cloning this repo https://github.com/CodeGenieApp/cloudfront-lambda-url-vs-apigw and running npm run init:dev
Locking down the Lambda FURL
If you want to enforce CloudFront security features such as Shield and WAF, you need to ensure attackers can’t bypass CloudFront by calling your Lambda Function URL directly. To accomplish this, AWS recommends setting the LFURL’s authorization to AWS_IAM and using OAC to grant access for the distribution. If only it were that simple. Unfortunately, there are two challenges with this:
- CloudFront overrides the
Authorization
header when invoking your Lambda Function, so you need to use a different, non-standard header to include your auth token. You can use CloudFront Functions to copy the originalAuthorization
header to a new header likeX-Auth
so that at least your clients can continue using the standard header name. - The client needs to sign the payload in PUT/POST methods. See this post by David Behroozi for more details.
A simpler solution proposed by Ryan Scott Brown is to have CloudFront add a custom origin header with a secret: 'X-CloudFront-Secret': 'NoBadGuysAllowed'
. Your Lambda Function can check the secret in the header, and return an error code if it doesn’t match. Another solution proposed by Andrea Amorosi uses Lambda@Edge. Since an attacker would need to discover both the Lambda Function URL and the secret before successfully bypassing CloudFront, this solution is likely more than enough for most applications.
Replacing API Gateway Authorizer with in-Lambda JWT validation
The only “advanced” API Gateway feature I use on all of my APIs is the Cognito/JWT Authorizer. Since CloudFront doesn’t have a similar native feature, we need to perform the JWT validation inside the Lambda Function. This has the added bonus of making our app more portable and easier to run locally, while also granting us more flexibility.
Performing Cognito JWT validation yourself is also incredibly fast (~4ms cold; ~0.3ms warm in Node.js) thanks to this tip by David Behroozi.
Another option is to use a CloudFront Function to perform JWT validation. This has the benefit of rejecting the request before it makes it to your Lambda Function, at the cost of a little extra complexity and having to do some magic to get local development working.
Conclusion
CloudFront + Lambda Function URL (CLFURL) is an excellent combination for Serverless APIs when you don’t need any of the advanced features offered by API Gateway. With CloudFront being an order of magnitude cheaper than API Gateway, it puts it more in line with what’d you’d expect when compared to the cost of other components of your Serverless architecture.
The minor performance improvement compared to HTTP API is nice, but likely to be unnoticeable to the end user (compared to REST API may be a different story). On the other hand, the increase in the max timeout will surely be appreciated by developers looking to add Generative AI to their API.
A repo is available at https://github.com/CodeGenieApp/cloudfront-lambda-url-vs-apigw. This repo was generated using Code Genie and modified to include CloudFront + Lambda Function URL and benchmarks. Check out the commit history to find the interesting parts.
Code Genie will soon offer CLFURL as an option when generating your source code, and will continue to support API Gateway HTTP APIs as well.
If you’ve found this blog useful, please give it a reshare! You can follow me and Code Genie on Twitter. Finally, if you’re building something new (or want to refresh something old), be sure to check out Code Genie. It takes care of all the boring parts of starting a new project so you can focus on what’s interesting!