Why does an OPTIONS request fail when the answer to the follow up request is a 204?

22,635

You have to also include the Access-Control-Allow-Origin in the response headers of the second request. That's not a client-side issue, but a backend one.

This behaviour is in accordance with the CORS specification, applied in the following explanation (section 7.1.5 "Cross-Origin Request with Preflight"):

  1. Preflight request (details omitted)
  2. "Set the cross-origin request status to preflight complete."
  3. "This is the actual request. (...) observe the request rules below while making the request."
    • If the response has an HTTP status code of 301, 302, 303, or 307 Not applicable
    • If the end user cancels the request Not applicable
    • If there is a network error Not applicable
    • Otherwise
      Perform a resource sharing check. If it returns fail, apply the cache and network error steps.

Your request already fails at the first step of the resource sharing check:

  1. If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.

I provide a simple NodeJS example illustrating your problem.
Your current backend behaves like:

require('http').createServer(function(request, response) {
    if (request.method == 'OPTIONS') { // Handle preflight
        response.writeHead(200, {
           "Access-Control-Allow-Origin": "*",
           "Access-Control-Allow-Headers": "X-Foo"
        });
    } else {                           // Handle actual requests
        response.writeHead(204, {
          //"Access-Control-Allow-Origin": "*"
        });
    }
    response.end();
}).listen(12345);

Now, make the request and experience a failure:

var x = new XMLHttpRequest;
x.open('GET', 'http://localhost:12345');
x.setRequestHeader('X-Foo','header to trigger preflight');
x.send();

Go back to the code I provided, and enable the Access-Control-Allow-Origin header in the response, and test again. Your request will now succeed.

Share:
22,635
GeorgieF
Author by

GeorgieF

Updated on July 09, 2022

Comments

  • GeorgieF
    GeorgieF almost 2 years

    I'm building a Backbone.js based app and face a strange issue.

    At a certain point the app requests a collection resource and inside Chrome (and Safari) I get an error like that:

    XMLHttpRequest cannot load http://api.mydomain.net/v1/foos/00000d/bars/000014/boots Origin http://localhost:3501 is not allowed by Access-Control-Allow-Origin.
    

    Ok, CORS issue I thought and blamed my API. Then requested this very resource via CURL:

    curl -i -H'Accept: application/json' -H'X-Auth-Token: pAWp5hrCmXA83GgFzgHC' -XOPTIONS 'http://api.mydomain.net/v1/foos/00000d/bars/000014/boots'
    HTTP/1.1 200 OK
    Status: 200
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Auth-Token
    Content-Length: 0
    

    looks good, now the GET:

    curl -i -H'Accept: application/json' -H'X-Auth-Token: pAWp5hrCmXA83GgFzgHC' -XGET 'http://api.mydomain.net/v1/foos/00000d/bars/000014/boots'
    HTTP/1.1 204 No Content
    Status: 204
    Cache-Control: no-cache
    Content-Length: 0
    Content-Type: text/plain
    

    In case I request boots collection that contain at least one object, everything works fine. The CORS headers my server responds with arr totally fine as I think. So why do the browsers report a cross origin resource problem?

    Is it due to the content type text/plain of my 204 responses?

    Preflight (OPTIONS) request in dev tools: OPTIONS

    Request headers of aborted response: GET