jotadepicas
jotadepicas

Reputation: 2493

Chrome is ignoring Access-Control-Allow-Origin header and fails CORS with preflight error when calling AWS Lambda

I'm building a ReactJS frontend that has to gather some data from AWS Lambdas using a JS fetch. I cannot make it work, no mater what CORS technique I apply. I've looked into other answers here to no avail.

I am definitely adding Access-Control-Allow-Origin with "*" value in my response (verified this using postman to call the endpoint). Also, Chrome complains about the preflight with Response to preflight request doesn't pass access control check, but no preflight request (OPTIONS method) is ever actually fired by chrome, all I see is the GET I'm trying to make on the first place, which is really confusing.

What am I missing? Why is chrome complaining about preflight when no OPTIONS preflight request is made? Why adding Access-Control-Allow-Origin with "*" in my response is not enough?

Thanks!

Upvotes: 5

Views: 10163

Answers (1)

jotadepicas
jotadepicas

Reputation: 2493

TL;DR: There was a preflight request happening, it just wasn't showing on chrome (there's a way to make them show up). Also, there's a tweak to make if you use custom headers for authorization tokens for example.

Summary

Well, after looking into this for a day and checking several other answers I'm posting this because none quite fit my problem, with the hope it will help anyone else facing this. First, I'll summarize the several parts involved in the error and then how to fix it, without resorting to any "hackish" solution like bypassing CORS with a chrome extension, or using any 3rd party service, like many posts suggest. My setup looked like this:

  • ReactJS frontend, try to make a GET request using fetch javascript method, running on http://localhost:3000 for development
  • AWS Lambda Backend, that answers the GET with a JSON payload (coded in python, not important but anyways). This lambda is adding Access-Control-Allow-Origin:"*" header to its response.
  • The above AWS Lambda is behind what AWS calls an "Authorizer", which is a function that runs before your lambda to check whatever authorization header you want to use to protect access to your API. This is important as we'll see later, because due to some nonsense on AWS inner workings, sometimes you cannot use the standard HTTP Authorization header, and it defaults to using authorizationToken as they suggest in their documentation and samples (and changing it not always works, there are plenty of users reporting this in their forums). We'll keep a note on this for later.
  • Both API methods (actual API and its Authorizer) routed and published on the internet using AWS API Gateway, which in short is a way to pair your lambda with a public URL to call it from elsewhere.
  • Google Chrome used as browser (with its developer tools enabled to monitor things)

The error

When trying to call the lambda, chrome blocks the GET request with this error showing on the console: Response to preflight request doesn't pass access control check. My lambda is already answering with the correct Access-Control-Allow-Origin header so, what's wrong? Also, no preflight OPTIONS request are being made anyways, so this was confusing.

Some debugging

AWS Lambda is great but their debugging tools are not as fluid as I would like, so I replaced the lambda with a local expressjs server, implementing just two methods: GET /foo and OPTIONS /foo. To my surprise, when from my ReactJS frontend I fetched /foo, it did call OPTIONS /foo first (I confirmed this by adding logs to my endpoint, etc, something you can also do in lambdas but its not as easy).

What was actually happening

A "preflight" request is an OPTIONS request to validate what is actually allowed when doing the following GET, but the Network tab in Chrome was not showing any OPTIONS request actually happening (I remember they used to show up here). Well, they changed it at some point, and now they are hidden by default. If you want them to show again (as a developer, I do), you can re-enable that by changing the out-of-blink-cors flag to disabled as explained here.

After changing this flag, now the OPTIONS request does show on the network tab. From there I could craft the OPTIONS response so it would enable the required GET afterwards. There are other considerations when using credentials and other cases (I found this article from Mozilla helpful with that), but in short my OPTIONS response headers look like this:

Access-Control-Allow-Origin: "http://localhost:3000"
Access-Control-Allow-Methods: "GET, POST, OPTIONS"
Access-Control-Allow-Headers: "authorizationToken"

(That last one, Access-Control-Allow-Headers, comes into play when dealing with AWS Lambdas Authorizers. If you are using that custom header to send your tokens, you need to allow it here).

After making CORS work locally, to solve it for my lambdas I did two things:

  1. You need your API Gateway to be able to answer OPTIONS request. There are a number of ways to achieve this, from writing your own lambda to answer it to having AWS to mock-response it for you. More info on that here.
  2. You need to make sure your GET lambda adds the Access-Control-Allow-Origin header, pointing to the same value your OPTIONS response did (In my case, http://localhost:3000).

After that, all worked as expected.

Final Notes

I wrote this answer because I found the conjunction of "React-CORS-AWS-Authorization" was not actually covered by any questions I found around.

Also there are a number of problems that may arise from using localhost for development on chrome, leading to suggestions of using an external service like lvh.me, but this was not the case, and some answers misleadingly relate this CORS problem to that. Moreover, some answers suggest disabling CORS checks altogether with some chrome extension, which is really bad security advice.

Finally, I found the idea of making a simple expressJS server to debug the server-side of things pretty helpful in understanding what was happening, because sometimes you simply cannot access what's happening on the other side, so maybe this suggestion might help people shorten the time dealing with things like this.

Upvotes: 9

Related Questions