31

I'm designing an API that allows the user to authenticate (using tokens) and that contains redirects within the same domain. Now, for an unauthenticated request to an endpoint that returns 303,

GET /documents/123  --> 303 redirect to `/documents/abc`
GET /documents/abc  --> 200

everything works out nicely.

Let's do an authenticated request to the same endpoint where the Authorization header is sent. This makes the request a preflighted request and the browser does a preflight OPTIONS request, i.e.

OPTIONS /documents/123   --> 204 (everything okay, please proceed)
GET /documents/123       --> 303 redirect to `/documents/abc`

At this point, instead of GETting the actual resource at /documents/abc, the browser yields

XMLHttpRequest cannot load http://localhost:8000/people/username/nschloe. 
The request was redirected to 'http://localhost:8000/people/YDHa-B2FhMie', 
which is disallowed for cross-origin requests that require preflight.

This behavior is in accordance with the standard:

7.1.5 Cross-Origin Request with Preflight

If the response has an HTTP status code that is not in the 2xx range

Apply the network error steps.

This seems to mean that one cannot do redirects for authenticated resources, even if the redirect is on the same domain (localhost).

Can this really be true? Is there a common workaround?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • What does you response headers look like? Especially Access-Control-Allow-Headers? – Sam Jan 22 '16 at 15:40
  • For the preflight `OPTIONS` request, I have `Access-Control-Allow-Headers:Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Accept-Encoding`. – Nico Schlömer Jan 22 '16 at 16:05
  • You mean this is for the redirected url /documents/abc right? – Sam Jan 22 '16 at 16:08
  • The request for the redirection is never exectuted; that's the problem. When a 303 is returned from `GET /documents/123`, an error is returned. – Nico Schlömer Jan 22 '16 at 16:14
  • Ok, then the issue is because there is a pre-flight request generated in second case, redirects won't be allowed. It is restricted by cross origin policy. It has nothing to do with Authorization header as such. – Sam Jan 22 '16 at 16:28
  • Indeed. I'm wondering if this is an oversight by the working group as I don't see how one would actually do authenticated redirects at all then. – Nico Schlömer Jan 22 '16 at 16:31
  • 2
    Well, they are averse to anything that causes a redirect as it might lead to security issues. You can always make another request from the client if the previous req was authorized. – Sam Jan 22 '16 at 16:37
  • 1
    The backend can of course accidentally redirect clients to other websites and include the token. I'd call that a (security) bug, and not something that needs to be prescribed by the standard. Redirecting to another resource (even if it's not `Same-Origin`) is a perfectly valid use case imho. – Nico Schlömer Jan 22 '16 at 16:49
  • @NicoSchlömer You might also want to update your question with a link to https://github.com/whatwg/fetch/issues/204 – sideshowbarker Jan 23 '16 at 04:53
  • @NicoSchlömer …and maybe also a link to https://lists.w3.org/Archives/Public/public-webappsec/2016Jan/thread.html#msg119 – sideshowbarker Jan 23 '16 at 04:59

1 Answers1

21

The original standard does preclude redirect after a successful CORS preflight. Quoting § 7.1.5.3:

This is the actual request. Apply the make a request steps and observe the request rules below while making the request.

  • If the response has an HTTP status code of 301, 302, 303, 307, or 308 Apply the cache and network error steps.

Due to your efforts (thanks!), on 2016-08-04 the standard was updated to allow redirect after successful CORS preflight check.

Until browsers catch up, the only feasible options seem to be one or a combination of:

  1. Issue redirects only for simple requests.
  2. Issue a 305 redirect, with your own URL in the Location header as the "proxy". Be prepared for limited browser support, as 305 is deprecated.
  3. Do a fake "redirect":
  • return HTML with meta refresh and/or Javascript Location change.
  • return HTML that has a viewport-filling iframe with the redirect target as the iframe's source.
  • display a link that the user has to click in order to access the content.
bishop
  • 37,830
  • 11
  • 104
  • 139
  • 2
    what is expected time to deliver changes like this one by browsers? Its still an issue in newest chrom/firefox. – hi_my_name_is Dec 24 '16 at 12:34
  • 2
    @freakman In my experience, there is about a two-year delay between standards change and implementation across latest versions of all major browsers. Specific browsers may update more quickly but across-the-board I'd say August 2018. – bishop Dec 24 '16 at 17:16
  • You can even generate a HTML page which auto clicks a hidden link. If HTTP POST is supported then even hidden form can be submitted to the destination endpoint – dvsakgec Jan 23 '17 at 19:04
  • 2
    A fix for this [has landed in the Blink/Chromium sources](https://chromium.googlesource.com/chromium/src/+/eaeb7a5f8e9432594d8bcc09956c1f50e8f0ba66) and will ship in Chrome 57 (targeted for release some time in mid-March I think). – sideshowbarker Feb 11 '17 at 13:03
  • @sideshowbarker Indeed. I just updated Chrome (now running 57) and the behaviour is now changed. Works well! – fabiomaia Mar 27 '17 at 22:19
  • Edge and Safari have already been updated to allow redirects for preflights, so now that Chrome allows it also, I think Firefox is the only one that doesn’t yet https://bugzilla.mozilla.org/show_bug.cgi?id=1346749 – sideshowbarker Mar 28 '17 at 00:29
  • I have Chrome version '58.0.3029.110 (64-bit)' and still getting the pre-flight issues. I have made a call from mytenant.sharepoint.com to msapproxy.net domain, which is redirected to login.microsoftonline.com. Any thoughts? – Nitin Rastogi May 13 '17 at 14:53
  • 5
    @NitinRastogi I think the problem you’re running into is a different issue. The change that was made to the spec and in Chrome 57 is that if the server responds with with 200 or 204 to a preflight OPTIONS and *then* responds to a subsequent GET with a 30x, then Chrome 57+ will now follow the redirect rather than emitting an error. But in your case the problem seems to that the server is responding to the OPTIONS request itself with a 302. Per the CORS (Fetch) spec, a 302 response to the OPTIONS request itself is not an acceptable response to a preflight. Hence the error you are seeing. – sideshowbarker May 13 '17 at 22:59
  • The 'redirect' you are talking about that talks place after CORS val;idation is an 'internal redirect' (not an external redirect) which is more akin to a FORWARD; it does not drop a thread and leave the DMZ; cors simple forwards to the internal endpoint – Orubel May 30 '23 at 19:20