loading...

CORS, preflighted requests & OPTIONS method

effingkay profile image Klaudia Updated on ・3 min read

Working on my side project, I came across a problem familiar to many - CORS. However, it wasn't the usual message I get when I forget to enable cross-origin on my backend. This one said:

Failed to load resource: Preflight response is not successful.
Fetch API cannot load <url>. Preflight response is not successful

This happened on the very first GET request I made to the server. I was fetching data from an API that was hosted on a different domain from my front-end. It was a simple request and the error left me very confused. After little troubleshooting, I found few solutions to my problem and learned quite a lot.

CORS - Quick intro

CORS or Cross-Origin Resource Sharing is a way for server to check if requests coming in are allowed if they're coming from a different origin. Meaning, if web application xyz.com makes a request to something.io, using either XMLHttpRequest or fetch API, CORS will use HTTP headers to tell the application if xyz.com has the right permission to access something.io. The permissions need to be configured at something.io, where you specify what methods and from what origins can come through.

exchange of headers between client and server

By default, for security, both XMLHttpRequest and fetch follow same-origin policy so if you haven't specifically configured CORS and the HTTP headers on your back-end, the requests from different domain will fail.

What is a preflight request?

When it comes to preflight, we can divide requests into two categories: simple requests and preflighted requests.

Simple requests

Some requests - called simple - don't trigger a preflight check. There is a simple exchange of CORS headers between client and server to check the permissions. For request to be simple, it needs to meet several criteria:

  • The allowed methods are
    • GET
    • HEAD
    • POST
  • The only headers which are allowed:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (but note the additional requirements below)
    • Last-Event-ID
    • DPR
    • Save-Data
    • Viewport-Width
    • Width
  • There are only three values allowed for Content-Type header for simple requests
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.
  • No ReadableStream object is used in the request.

Preflighted requests

Now, if the request doesn't meet the criteria above, the browser automatically sends a HTTP request before the original one by OPTIONS method to check whether it is safe to send the original request. Most common cases are if requests have DELETE, PUT or any other method that can amend data, any headers that are not CORS-safelisted (listed above) or Content-Type of, let's say application/json.

So, let's say we send a DELETE request to the server. The browser sends OPTIONS request with headers containing info about the DELETE request we made.

OPTIONS /users/:id 
Access-Control-Request-Method: DELETE 
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://example.com

The server then makes a check and if it allows the request, it responds with status code 200 OK.

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE

After this check, the browser will send the original DELETE request we made in the first place.

The error and how to fix it

You have several options, really. The easiest option would be to avoid the preflight request altogether by making sure your request falls into the simple category. Go through your settings and headers and remove or change any complex headers that aren't needed.

However, this only solves small percentage of cases. My app is authenticated and I'm sending Authorization header with tokens by PUT and DELETE methods. I am using cors npm package that enables CORS in my Express app so I checked their documentation and they also provide OPTIONS handling. All's fixed just with one line.

Final words

If you want to learn more, I'd recommend the MDN's articles on CORS and OPTIONS. This article was very useful when I was trying to eliminate preflight, despite some advices like Moving Authorization to the query string seemed little extreme and for some reason very uncomfortable.

I hope you enjoyed this article, if you have any questions or notes, just comment bellow ;)

Posted on by:

Discussion

markdown guide
 

The easiest option would be to avoid the preflight request altogether by making sure your request falls into the simple category. Go through your settings and headers and remove or change any complex headers that aren't needed. Is application/json really necessary in your case? Change it to text/plain.

This is a bad solution and does not solve the inner problem.

Preflights requests exist as an extra layer of security when manipulating an API, and sending an invalid mime type only adds to the confusion.

Adding OPTIONS support to your backend is the best solution, as long as you don't do something silly like always returning 200 for options query.

 

That's actually a good point, I didn't mean to say that you should be sending invalid or wrong types just so you avoid preflight (although I see how that came across and I changed it slightly) but I think having preflight requests for every single request you make can be an overkill.

I was thinking about the case when I added content-type: application/json to every request on my front end even when it was not necessary and that resulted in unnecessary preflight requests for simple GET request which should have been allowed by already enabled CORS.

And thank you for pointing this out :)

 

So am I supposed to handle OPTIONS separately on my backend? Somehow I've never bothered to do that and the APIs have turned out fine ... 😟

 

You know, this was the first time I've encountered this problem. Until a few weeks ago, I haven't even heard of OPTIONS method to be completely honest and I did authenticated APIs before. I guess it depends how you handle CORS in the first place, maybe some packages manage preflight automatically or maybe you don't even need it because you don't make any OPTIONS requests. It all depends on your user case.

 

Thank you!! I was having trouble with a project and this simple guide explained everything that MDN, Google Docs and StackOverflow couldn't say simply.

 
 

This was a great explanation! :)