1

I'm implementing CORS on a server and I'm having the following error inside the chrome console when requesting a route that does not exist:

Access to fetch at 'https://localhost:5000/unknown_route'
from origin 'https://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.

Should the server implementing CORS always return a 200 (or 204) for OPTIONS methods, regardless of a potential error after ?

I've found this question, but it's only stating that CORS headers should be present (which is my case). The answer to this other question states that 200 should be the status, but does not specifically mention 404 errors.

HHK
  • 1,352
  • 1
  • 15
  • 25
  • Linking an interesting issue on flask-cors requesting just that: https://github.com/corydolphin/flask-cors/issues/262 – HHK Oct 14 '20 at 11:56
  • You're implementing this server, so I believe this is a question of what behaviour you want, rather than what the spec requires. Do you want the browser to go ahead and make that request, and receive a 404, or not? – Joe Oct 14 '20 at 12:02
  • What I want here is to understand what the spec advocates. As linked in the above comment, flask-cors is not implementing this, and I'd like to understand what should the standard be. If the status should always be 200, then I can advocate for a change on the flask-cors project and submit a PR. – HHK Oct 14 '20 at 12:07
  • 1
    The spec doesn’t advocate anything special with regard to this. https://fetch.spec.whatwg.org/#ref-for-ok-status is all that the spec says with regard to the status code — and that simply says that if the response has a 2xx status code, then the preflight succeeds. – sideshowbarker Oct 14 '20 at 12:21

2 Answers2

3

I don't believe that the CORS protocol specifies behaviour here, although it implies, with:

A CORS-preflight request is a CORS request that checks to see if the CORS protocol is understood.

(my emphasis) that this is a check on whether CORS is understood, and not a place to enforce validation.

In general, it's up to the server implementor to decide whether they want to permit the request to go ahead, and return an appropriate status code and response, or block the request.

In the linked issue:

I want to show users of my webapp a "Resource not found" message when the requested path is not found (404)

If this is what you want, you should permit CORS, so this shows up as a 404 rather than a blocked request.

Joe
  • 29,416
  • 12
  • 68
  • 88
  • 2
    Absolutely. The whole idea of CORS restrictions is _not_ showing the info to unauthorized consumers. Now, this may or may not be convenient, but it doesn't seem to be a good idea to mix those concerns. – raina77ow Oct 14 '20 at 13:35
1

Spec doesn't care - and actually shouldn't care about that. Your server shouldn't care either - and just resolve the CORS part of its response as soon as possible.


See, when user agent does Preflight request, it (by spec) is expected to only care whether it's successful or not.

If it is successful, the result is usually placed into a so called CORS-preflight Cache to be employed in subsequent requests with matching methods, urls and headers.

But if it is not, these cache entries will be cleared - regardless of the reason. Oh, and main request won't be processed too, of course.

Now, the spec goes into much details describing what exactly is considered success:

  • response passes CORS Check
  • response status is ok status (any status in the range 200 to 299, inclusive)
  • Access-Control-Allow-Methods and Access-Control-Allow-Headers headers are there, their values match expectations...

... and there's a lot more there. But the point is, failure at any step always results in "...then return a network error" result.

And this is how network error looks like:

A network error is a response whose status is always 0, status message is always the empty byte sequence, header list is always empty, and body is always null.

Did I say spec doesn't care?


It might be interesting to check how Chromium implements that spec:

std::unique_ptr<PreflightResult> CreatePreflightResult(/* args skipped */) {
  const int response_code = head.headers ? head.headers->response_code() : 0;

  *detected_error_status = CheckPreflightAccess(
      final_url, response_code,
      GetHeaderString(head.headers, header_names::kAccessControlAllowOrigin),
      GetHeaderString(head.headers, 
                      header_names::kAccessControlAllowCredentials),
      original_request.credentials_mode,
      tainted ? url::Origin() : *original_request.request_initiator);

  if (*detected_error_status)
    return nullptr;

  base::Optional<mojom::CorsError> error;
  error = CheckPreflight(response_code);
  if (error) {
    *detected_error_status = CorsErrorStatus(*error);
    return nullptr;
  }

  if (original_request.is_external_request) {
    *detected_error_status = CheckExternalPreflight(GetHeaderString(
        head.headers, header_names::kAccessControlAllowExternal));
    if (*detected_error_status)
      return nullptr;
  }

  auto result = PreflightResult::Create(
      original_request.credentials_mode,
      GetHeaderString(head.headers, header_names::kAccessControlAllowMethods),
      GetHeaderString(head.headers, header_names::kAccessControlAllowHeaders),
      GetHeaderString(head.headers, header_names::kAccessControlMaxAge),
      &error);

  if (error)
    *detected_error_status = CorsErrorStatus(*error);
  return result;
}

As you can see, this...

return nullptr;

... is a leitmotif of the function: whenever a check is failed, the function immediately returns null pointer. True, detected_error_status is filled as well, but it seems to be there mostly for debugging purposes (i.e., to see at which step check had failed - and design the console warning accordingly).

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • "Spec doesn't care - and actually shouldn't care about that. Your server shouldn't care either - and just resolve the CORS part of its response as soon as possible." Ok so if the route does not exists, it should not care about that, resolve the CORS headers and return a 200 right ? – HHK Oct 14 '20 at 14:30
  • Right. Unless your API is segmented, and CORS is open just for some segments, OPTIONS request should be resolved on topmost level. In fact, that's how it's done sometimes with nginx-based configurations: nginx is completely responsible for processing preflights. Like [here](https://enable-cors.org/server_nginx.html), for example. – raina77ow Oct 14 '20 at 14:35
  • As for specific code, 204 No Content seems to be more semantic. – raina77ow Oct 14 '20 at 14:39