55

It looks like according to the CORS Spec, GET and POST requests should transparently follow 302 redirects. But Chrome is canceling my request.

Here's the JS that does the request:

var r = new XMLHttpRequest();
r.open('GET', 'https://dev.mysite.com/rest', true);
r.send();

Here's what should happen:

  1. Client: XHR POST request to /rest
  2. Server: responds with HTTP 302 redirect to /rest/
  3. Client: Follow that redirect

But after step 2, Chrome cancels the request. If there was no HTTP 302, the request would work perfectly. I've confirmed this.

When the request runs, I can see in Chrome's Network panel only one XHR -- a canceled POST request with no response headers or response body.

Debugging with Chrome's net-internals tool, I see that there was a response sent from the server, and after that, the request was cancelled. Here is the output of the request:

79295: URL_REQUEST
https://dev.mysite.com/rest
Start Time: 2013-08-30 12:41:11.637

t=1377880871637 [st=    0] +REQUEST_ALIVE  [dt=13455]
t=1377880871638 [st=    1]    URL_REQUEST_BLOCKED_ON_DELEGATE  [dt=1]
                              --> delegate = "extension Adblock Plus"
t=1377880871639 [st=    2]   +URL_REQUEST_START_JOB  [dt=13453]
                              --> load_flags = 143540480 (DO_NOT_SAVE_COOKIES | DO_NOT_SEND_AUTH_DATA | DO_NOT_SEND_COOKIES | ENABLE_LOAD_TIMING | MAYBE_USER_GESTURE | REPORT_RAW_HEADERS | VERIFY_EV_CERT)
                              --> method = "POST"
                              --> priority = 2
                              --> upload_id = "0"
                              --> url = "https://dev.mysite.com/rest"
t=1377880871639 [st=    2]      HTTP_CACHE_GET_BACKEND  [dt=0]
t=1377880871639 [st=    2]     +HTTP_STREAM_REQUEST  [dt=7]
t=1377880871646 [st=    9]        HTTP_STREAM_REQUEST_BOUND_TO_JOB
                                  --> source_dependency = 79296 (HTTP_STREAM_JOB)
t=1377880871646 [st=    9]     -HTTP_STREAM_REQUEST
t=1377880871646 [st=    9]     +HTTP_TRANSACTION_SEND_REQUEST  [dt=0]
t=1377880871646 [st=    9]        HTTP_TRANSACTION_SEND_REQUEST_HEADERS
                                  --> GET /facultyportfolio-rest HTTP/1.1
                                      Host: dev.liberty.edu
                                      Connection: keep-alive
                                      Content-Length: 46
                                      Origin: http://localhost:8080
                                      User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36
                                      Content-Type: application/json; charset=UTF-8
                                      Accept: */*
                                      Referer: http://localhost:8080/ajaxtest.html
                                      Accept-Encoding: gzip,deflate,sdch
                                      Accept-Language: en-US,en;q=0.8
t=1377880871646 [st=    9]        HTTP_TRANSACTION_SEND_REQUEST_BODY
                                  --> did_merge = true
                                  --> is_chunked = false
                                  --> length = 46
t=1377880871646 [st=    9]     -HTTP_TRANSACTION_SEND_REQUEST
t=1377880871646 [st=    9]     +HTTP_TRANSACTION_READ_HEADERS  [dt=1001]
t=1377880871646 [st=    9]        HTTP_STREAM_PARSER_READ_HEADERS  [dt=1000]
t=1377880872646 [st= 1009]        HTTP_TRANSACTION_READ_RESPONSE_HEADERS
                                  --> HTTP/1.1 302 Found
                                      Date: Fri, 30 Aug 2013 16:41:11 GMT
                                      Server: Apache/2
                                      Access-Control-Allow-Origin: http://localhost:8080
                                      Access-Control-Allow-Credentials: true
                                      Location: https://dev.mysite.com/rest/
                                      Content-Language: en-US
                                      Vary: Accept-Encoding,User-Agent
                                      Content-Encoding: gzip
                                      Content-Length: 20
                                      Connection: close
                                      Content-Type: text/plain; charset=UTF-8
t=1377880872647 [st= 1010]     -HTTP_TRANSACTION_READ_HEADERS
t=1377880872647 [st= 1010]     +URL_REQUEST_BLOCKED_ON_DELEGATE  [dt=12445]
t=1377880885091 [st=13454]        CANCELLED
t=1377880885092 [st=13455]   -URL_REQUEST_START_JOB
                              --> net_error = -3 (ERR_ABORTED)
t=1377880885092 [st=13455] -REQUEST_ALIVE

At the end, you can see "Cancelled" because of "URL_REQUEST_BLOCKED_ON_DELEGATE". I don't know what that means. But again, if there was no HTTP 302 redirect, the error would not occur.

Does anyone know what is causing Chrome to cancel this request?

Matthias
  • 13,607
  • 9
  • 44
  • 60

4 Answers4

33

The answers in here are mixed, hinting on certain settings in code etc. which may solve the redirect problem with CORS, but the CORS spec clearly specifies when such CORS redirects will fail/pass : As per the spec, browsers should

  1. Allows 3XX redirect , if the request to the redirected resource doesn't require pre-flight check (simple CORS requests without custom header for example). See https://www.w3.org/TR/cors/#simple-cross-origin-request-0

If the manual redirect flag is unset and the response has an HTTP status code of 301, 302, 303, 307, or 308 Apply the redirect steps

  1. Don't allow 3XX redirect, if the request to redirected resource requires pre-flight check. See https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0

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

I have explored various CORS scenarios in github repo: https://github.com/monmohan/cors-experiment.

This specific issue with failed redirect can also be easily reproduced in isolation by the bundle here: https://github.com/monmohan/cors-experiment/tree/master/issue

factotum
  • 900
  • 10
  • 13
  • 1
    Do you happen to know why 30x is disallowed for requests requiring preflight? I'd love to read some rationale for that part of the spec. – mgol Aug 30 '16 at 11:03
  • Apparently the redirect being allowed is a "recent change" (http://stackoverflow.com/questions/40580913/fetch-api-custom-request-headers-cors-and-cross-origin-redirects) in the spec, only August 4 2016, so it's taking awhile for browsers to come into compliance... – rogerdpack Aug 16 '17 at 21:06
15

I found this post about setting the correct Access-Control-Allow-Origin CORS header on your 302 response to be helpful, at least in my similar-sounding case.

Investigation of the problem showed that his XHR was not landing on the CORS-enabled URL directly, but was being redirected to it through an HTTP 302 (redirect) response.

So bear in mind that the redirecting URL must also include an Access-Control-Allow-Origin header, else the browser will stop right there with its attempted cross-domain request.

I've also found that setting additional CORS headers above and beyond Access-Control-Allow-Origin will often result in a cancelled transaction.

Leigh
  • 28,765
  • 10
  • 55
  • 103
  • 1
    My request did receive a 302, but the response contained a proper `Access-Control-Allow-Origin` header. You said that additional response headers may cause the cancellation? Can you explain that a little more, or give examples of which headers cause it? – Matthias Nov 19 '13 at 21:13
11

http://httpstatus.es/302

If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

idbehold
  • 16,833
  • 5
  • 47
  • 74
  • So that seems to say that GET requests should redirect. My request is a GET, but it doesn't redirect. – Matthias Sep 03 '13 at 13:54
  • 2
    You said step 1 was, "Client: XHR **POST** request to /rest" – idbehold Sep 03 '13 at 15:58
  • 2
    Oh sorry, yes, the problem was happening with POST initially, but also with GET. Maybe switching to a 303 or 307 HTTP code might work... – Matthias Sep 04 '13 at 16:42
  • 2
    See http://stackoverflow.com/questions/40580913/fetch-api-custom-request-headers-cors-and-cross-origin-redirects for a clearer explanation and spec update. – jprosevear Feb 04 '17 at 01:59
3

I also had the problem that Chrome was not following a redirect on a CORS request. For me the problem was that the JS-framework I use (Sencha Touch) adds a request header: X-Requested-With: "XMLHttpRequest"

As soon as I removed this (in Sencha Touch by calling Ext.Ajax.setUseDefaultXhrHeader(false);) it worked like a charm.

Not sure why but I hope this information helps someone.

RickyA
  • 15,465
  • 5
  • 71
  • 95
Ronald
  • 2,864
  • 3
  • 25
  • 36