Fixing up CORS errors in HTTP API
Tags: aws, security, serverless
Published: 2022-09-29 06:00:52

When we create a single route for multiple request types, we might receive a CORS error from browser requests. It's easy to generate a mock integration for preflight requests in our HTTP API using Lambda functions.

## 1. Problem statement

Alice has been building an new service using an <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html" class="link" target="_blank" rel="noopener noreferrer">HTTP API Gateway</a>. A <a href="https://reactjs.org/" class="link" target="_blank" rel="noopener noreferrer">React</a> 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 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="link" target="_blank" rel="noopener noreferrer">CORS</a> 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:

![HTTP API CORS configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7bvbo7a2m4tvn6gtcee.png)

It allows requests from `localhost:3000` for front-end development. The configuration contains the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" class="link" target="_blank" rel="noopener noreferrer">HTTP request methods</a> 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**.

<a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html" class="link" target="_blank" rel="noopener noreferrer">REST API Gateways</a> 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:

```js
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.

![Preflight response headers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/254nfyduk6cohbfpvcsu.png)

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

<a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html" class="link" target="_blank" rel="noopener noreferrer">Choosing between REST APIs and HTTP APIs</a> - Comparison with lots of tables

<a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html" class="link" target="_blank" rel="noopener noreferrer">Configuring CORS for an HTTP API</a> - It is exactly what the title says

<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="link" target="_blank" rel="noopener noreferrer">Cross-Origin Resource Sharing (CORS)
</a> - Overview of CORS