Your CORS and API Gateway survival guide
We're Serverless!
Posted on March 15, 2022
Originally posted at Serverless
Building web API backends is one of the most popular use cases for Serverless applications. You get the benefit of a simple, scalable backend without the operations overhead.
However, if you have a web page that’s making calls to a backend API, you’ll have to deal with the dreaded Cross-Origin Resource Sharing, or CORS. If your web page makes an HTTP request to a different domain than you’re currently on, it needs to be CORS-friendly.
If you’ve ever found yourself with the following error:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource
then this page is for you!
In this post, we’ll cover all you need to know about Serverless + CORS. If you don’t care about the specifics, hit the TL;DR section below. Otherwise, we’ll cover:
Let’s get started!
TL;DR
If you want the quick and dirty way to solve CORS in your Serverless application, do this.
- To handle preflight requests, add the cors: true flag to each HTTP endpoint in your serverless.yml:
- To handle the CORS headers, return the CORS headers in your response. The main headers are Access-Control-Allow-Origin and Access-Control-Allow-Credentials.
You can use the example below, or check out the middleware libraries discussed below to help with this:
- If you’re using a custom authorizer, you’ll need to add the following CloudFormation in your resources block of serverless.yml:
If you’re not making a “simple request”, your browser will send a preflight request to the resource using the OPTIONS method. The resource you're requesting will return with methods that are safe to send to the resource and may optionally return the headers that are valid to send across.
Let’s break that down.
When does my browser send a preflight request?
Your browser will send a preflight request on almost all cross-origin requests. (The exceptions are “simple requests”, but it’s a pretty narrow subset of requests.)
Basically, a simple request is only a GET request or a POST request with form data that has no authentication. If you're outside of that, it will need a preflight.
If you use a PUT or DELETE request, it will send a preflight. If you use a Content-Type header outside of application/x-www-form-urlencoded, multipart/form-data, or text/plain, it will send a preflight. If you include any headers outside some very basic ones, such as Authentication headers, it will send a preflight.
What’s in the response to the preflight request?
The response to a preflight request includes the domains it allows to access the resources and the methods it allows at that resource, such as GET, POST, PUT, etc. It may also include headers that are allowed at that resource, such as Authentication.
How do I handle preflight requests with Serverless?
To set up the preflight response, you’ll need to configure an OPTIONS method handler at your endpoint in API Gateway. Fortunately, this is very simple with the Serverless Framework.
Simply add cors: true to each endpoint in your serverless.yml:
This configures API Gateway to allow any domain to access, and it includes a basic set of allowed headers. If you want additional customization (advanced usage only), it will look like this:
CORS Response Headers
While the preflight request only applies to some cross-origin requests, the CORS response headers must be present in every cross-origin request. This means you must add the Access-Control-Allow-Origin header to your responses in your handlers.
If you’re using cookies or other authentication, you’ll also need to add the Access-Control-Allow-Credentials header to your response.
To match the serverless.yml in the section above, your handler.js file should look like:
Note how the response object has a headers property, which contains an object with Access-Control-Allow-Origin and Access-Control-Allow-Credentials.
It can be a real pain to add these headers everywhere in your function, particularly if you have multiple logical paths. Luckily, there are some nice tools to help with this!
If you use Javascript, check out the Middy middleware engine for use with Lambda. It has a lot of nice middlewares that handle the boring boilerplate of your Lambda functions. One is the cors middleware, which automatically adds CORS headers to your functions.
A basic example looks like this:
Perfect — automatic CORS headers! Check out the whole Middy library for lots of other nice utilities.
If you’re a Pythonista, Daniel Schep has made a nice lambda-decorators library with the same goals as Middy—replacing Lambda boilerplate.
Here’s an example of using it in your Python functions:
Note: Daniel is the creator of the serverless-python-requirements package, which you should absolutely be using if you're writing Lambda functions in Python. Check out our previous blog post on Python packaging.
CORS with custom authorizers
Custom authorizers allow you to protect your Lambda endpoints with a function that is responsible for handling authorization.
If the authorization is successful, it will forward the request onto the Lambda handler. If it’s unsuccessful, it will reject the request and return to the user.
The CORS difficulty lies in the second scenario — if you reject an authorization request, you don’t have the ability to specify the CORS headers in your response. This can make it difficult for the client browser to understand the response.
To handle this, you’ll need to add a custom GatewayResponse to your API Gateway. You’ll add this in the resources block of your serverless.yml:
This will ensure that the proper response headers are returned from your custom authorizer rejecting an authorization request.
CORS with Cookie credentials
Note: This section was added on January 29, 2018 thanks to a request from Alex Rudenko. Hat tip to Martin Splitt for a great article on this issue.
In the examples above, we’ve given a wildcard “” as the value for the Access-Control-Allow-Origin header. However, if you're making a request using credentials, the wildcard value is not allowed. For your browser to make use of the response, the Access-Control-Allow-Origin response headers *must include the specific origin that made the request.
There are two ways you can handle this. First, if you only have one origin website that’s making the request, you can just hardcode that into your Lambda function’s response:
If you have multiple origin websites that may be hitting your API, then you’ll need to do a more dynamic approach. You can inspect the origin header to see if its in your list of approved origins. If it is, return the origin value in your Access-Control-Allow-Origin header:
In this example, we check if the origin header matches one of our allowed headers. If so, we include the specific origin in our Access-Control-Allow-Origin header, and we state that Access-Control-Allow-Credentials are allowed. If the origin is not one of our allowed origins, we include the standard headers which will be rejected if the origin attempts a credentialed request.
Conclusion
CORS can be a pain, but there are a few straightforward steps you can take to make it much easier to deal with.
You know what that means. Goodbye forever, inexplicable No 'Access-Control-Allow-Origin' header is present on the requested resource error. 👋
Posted on March 15, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.