Fixing up CORS errors in HTTP API
1. Problem statement
Alice has been building an new service using an HTTP API Gateway. A React application will call the endpoints, and she will use tokens in the Authorization
header to authorize the requests with API Gateway.
She configured a custom domain and set up CORS in the API Gateway because most requests will come from the browser.
All went well until she added the ANY /{proxy+}
catch-all route. After she had added the authorizer to it and invoked the endpoint with the GET /todos
request, she received the following error:
Access to XMLHttpRequest at 'https://SUBDOMAIN.DOMAIN_NAME/todos' from
origin 'http://localhost:3000' has been blocked by CORS policy: Response
to preflight request doesn't pass access control check: It does not have
HTTP ok status.
What can she do now?
2. Options
The CORS configuration she set up in API Gateway looks like this:
It allows requests from localhost:3000
for front-end development. The configuration contains the HTTP request methods the application will use. The allowed headers look good, too.
The endpoint still responds with the above CORS error.
By default, API Gateway will automatically send a response to preflight OPTIONS requests. As long as we specify the method in the route, e.g., GET /todos
or POST /todos
, we won’t have any CORS issues. But when we use ANY
in the route, we will receive the error.
If we leave the endpoint unprotected and remove the authorizer, we will have a successful request. The problem is that we should protect the route so only authenticated users can call it.
3. A solution
Instead, we can create an unauthorized OPTIONS
route with custom integration.
We can add the original integration to OPTIONS
, but it’s better to consider a mock integration.
REST API Gateways support mock integrations, which we can use for the OPTIONS
routes. Unfortunately, we don’t have built-in mock integrations for HTTP APIs.
It means that we have to create one. It’s easy to do so, and a simple Lambda function will do the job:
exports.handler = async (event) => {
const response = {
statusCode: 204,
};
return response;
};
We can attach this function to the OPTIONS
as an integration.
4. ANY routes
It’s not just the ANY /{proxy+}
route that fails with the CORS error but other ANY
routes, too. For example, the ANY /todos
endpoint will also return the CORS error if we protect it with an authorizer.
The solution here is the same as above, create an OPTIONS
route and add the preflight handler Lambda function as an integration. We can reuse the same function for multiple ANY
routes.
5. The API settings will play
If we hit the ANY
endpoint with the https://SUBDOMAIN.DOMAIN_NAME/todos
request from the browser application, the response headers will contain the parameters we have set in the CORS section of the API Gateway.
It is because API Gateway will ignore the integration response. The preflight handler here only returns a 204
status code, which will be the response’s HTTP status. Everything else will come from the settings we specified for CORS.
6. Summary
Catch-all routes will respond with a CORS error unless we specify an unauthorized OPTIONS
endpoint. Because this endpoint is unprotected, we should attach a mock integration to this route. HTTP API doesn’t have the concept of mock integrations, so it’s worth creating one and using that for the OPTIONS
request.
7. Further reading
Choosing between REST APIs and HTTP APIs - Comparison with lots of tables
Configuring CORS for an HTTP API - It is exactly what the title says
Cross-Origin Resource Sharing (CORS) - Overview of CORS