Handle CORS in Serverless APIs
Let’s take stock of our setup so far. We have a Serverless API backend that allows users to create notes and an S3 bucket where they can upload files. We are now almost ready to work on our frontend React app.
However, before we can do that. There is one thing that needs to be taken care of — CORS or Cross-Origin Resource Sharing.
Since our React app is going to be run inside a browser (and most likely hosted on a domain separate from our Serverless API and S3 bucket), we need to configure CORS to allow it to connect to our resources.
Let’s quickly review our backend app architecture.
Our client will be interacting with our API, S3 bucket, and User Pool. CORS in the User Pool part is taken care of by its internals. That leaves our API and S3 bucket. In the next couple of chapters we’ll be setting that up.
Let’s get a quick background on CORS.
Understanding CORS
There are two things we need to do to support CORS in our Serverless API.
-
Preflight OPTIONS requests
For certain types of cross-domain requests (PUT, DELETE, ones with Authentication headers, etc.), your browser will first make a preflight request using the request method OPTIONS. These need to respond with the domains that are allowed to access this API and the HTTP methods that are allowed.
-
Respond with CORS headers
For all the other types of requests we need to make sure to include the appropriate CORS headers. These headers, just like the one above, need to include the domains that are allowed.
There’s a bit more to CORS than what we have covered here. So make sure to check out the Wikipedia article for further details.
If we don’t set the above up, then we’ll see something like this in our HTTP responses.
No 'Access-Control-Allow-Origin' header is present on the requested resource
And our browser won’t show us the HTTP response. This can make debugging our API extremely hard.
Preflight Requests in API Gateway
To ensure that API Gateway responds to the OPTIONS requests, we need to add the following to our Lambda function definitions in our serverless.yml
.
cors: true
Let’s do that for all of our functions.
Replace the functions block at the bottom of our serverless.yml
with the following.
functions:
# Defines an HTTP API endpoint that calls the main function in create.js
# - path: url path is /notes
# - method: POST request
# - authorizer: authenticate using the AWS IAM role
create:
handler: create.main
events:
- http:
path: notes
method: post
cors: true
authorizer: aws_iam
get:
# Defines an HTTP API endpoint that calls the main function in get.js
# - path: url path is /notes/{id}
# - method: GET request
handler: get.main
events:
- http:
path: notes/{id}
method: get
cors: true
authorizer: aws_iam
list:
# Defines an HTTP API endpoint that calls the main function in list.js
# - path: url path is /notes
# - method: GET request
handler: list.main
events:
- http:
path: notes
method: get
cors: true
authorizer: aws_iam
update:
# Defines an HTTP API endpoint that calls the main function in update.js
# - path: url path is /notes/{id}
# - method: PUT request
handler: update.main
events:
- http:
path: notes/{id}
method: put
cors: true
authorizer: aws_iam
delete:
# Defines an HTTP API endpoint that calls the main function in delete.js
# - path: url path is /notes/{id}
# - method: DELETE request
handler: delete.main
events:
- http:
path: notes/{id}
method: delete
cors: true
authorizer: aws_iam
billing:
# Defines an HTTP API endpoint that calls the main function in billing.js
# - path: url path is /billing
# - method: POST request
handler: billing.main
events:
- http:
path: billing
method: post
cors: true
authorizer: aws_iam
This will add the basic set of CORS headers in any OPTIONS requests made to these endpoints. To customize the headers that are sent, you can do something like this:
cors:
origin: '*'
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
allowCredentials: false
But for our purposes, we’ll just use the default option.
CORS Headers in Lambda Functions
Next we need to add the CORS headers in our Lambda function response.
Replace the return
statement in our libs/handler-lib.js
.
return {
statusCode,
body: JSON.stringify(body),
};
With the following.
return {
statusCode,
body: JSON.stringify(body),
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true,
},
};
Again you can customize the CORS headers but we’ll go with the default ones here.
Let’s quickly test the above. Run the following in your project root.
$ serverless invoke local --function list --path mocks/list-event.json
You should see something like this in your terminal.
{
"statusCode": 200,
"body": "[{\"attachment\":\"hello.jpg\",\"content\":\"hello world\",\"createdAt\":1602891322039,\"noteId\":\"42244c70-1008-11eb-8be9-4b88616c4b39\",\"userId\":\"123\"}]",
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true
}
}
Notice that we are now returning the CORS headers.
The two steps we’ve taken above ensure that if our Lambda functions are invoked through API Gateway, it’ll respond with the proper CORS config.
However, there are cases where API Gateway might run into an error and our Lambda functions are not invoked. For example, if the authentication information is invalid. In these cases we want API Gateway to respond with the right CORS headers as well.
Let’s look at that next.
For help and discussion
Comments on this chapter