Fixing issue in making cross domain Ajax call to SharePoint REST service in Chrome

This post is a contribution from Jing Wang, an engineer with the SharePoint Developer Support team
Symptom:
Remote Ajax Application is configured with Windows Authentication. It makes XMLHttpRequest to SharePoint 2013 Web Service, listdata.svc.
Sample code:

 <!DOCTYPE html>
<html>
<head>
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js" type="text/javascript" ></script>
<script src="https://code.jquery.com/jquery-1.9.1.js" type="text/javascript" ></script>
</head>
<body>
<h1>test page</h1>
                <script type="text/javascript">
                 //Ajax call to use listdata.svc
            var restUrl = "https://SharePointSiteUrl/_vti_bin/listdata.svc/List1";
            
  $.ajax({
                url: restUrl,
                type: "GET",
                dataType: 'JSON',
         headers: {
                         "Content-Type": "'application/json;odata=verbose'",
                         "Accept": "application/json;odata=verbose",
                         "crossDomain": "true",
                         "credentials":"include"
                         },
                xhrFields: { withCredentials: true        },
                success: function(response) {
                    alert("Success");
                },
                error: function(response){
                    alert("Error" );
                }
            });
                    </script>
</body>
</html>

When use Chrome to browse to the above page, you will see the below error
Failed to load resource: the server responded with a status of 401 dev.contoso.com/_vti_bin/listdata.svc/EMSPropertyLibrary()?$filter......
(Unauthorized)
Below is screen shot of error in Browser Developer tool console window :

Cause:
The XMLHttpRequest was sent with added custom headers, like,
headers.append('Content-Type', 'application/json;odata=verbose');
headers.append('credentials', 'include');
These custom headers made the request NOT a "Simple Request", see reference, https://developer.mozilla.org/en-US/docs/Web/HTTP/Access\_control\_CORS
Since the request here with header ('Content-Type', 'application/json;odata=verbose'), it is not a Simple Request and the following process will happen.

  1. Browser (Chrome) sent preflight OPTIONS request to SharePoint WFE server, which hosts the listdata.svc, without credential first,
  2. Server returned HTTP/1.1 401 Unauthorized response for the preflight request.
  3. Due to 401 Unauthorized response from server the actual Web Service request will get dropped automatically.

Fiddler trace shows:

Solution:

Step I,

Force SharePoint WFE Server to send Http Status code of 200 for the preflight requests by us IIS’s new URL Rewrite tool:

  1. Install "web platform installer" from https://www.microsoft.com/web/downloads/platform.aspx
  2. Go to "Applications" tab and search for "URL Rewrite" and download it
  3. Open IIS configuration tool (inetmgr) and select the root node having the machine name in the IIS. Double click "URL Rewrite" in the features view on the right hand side.
  4. Add a new blankrule by clicking on Add Rule --> New Blank Rule from the menu on the right
  5. Give it any name
  6. In "Match URL", specify this pattern: .*
  7. In "Conditions" click on Add and  specify this condition entry: {REQUEST_METHOD} and this pattern: ^OPTIONS$
  8. In "Action", specify: action type Personalized response (or Customized reponse), state code 200, reason Preflight, description Preflight
  9. Click on Apply

Now, the server should reply with a 200 status code response to the preflight request, regardless of the authentication.

Step II,
Since this is a CORS request, above change is not enough to make the XMLHttpRequest call go through.
With the changes in Step I, Chrome Browser console shows a different error:
(index):1 XMLHttpRequest cannot load https://***/_vti_bin/listdata.svc...
Request header field crossDomain is not allowed by Access-Control-Allow-Headers in preflight response.

Make the following changes to the web.config for the SharePoint Web Application, to allow more custom headers required to enable CORS:

Sample code block in Web.Config. You will need to update the value of Access-Control-Allow-Origin to point to your remote ajax application.
----

 <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="https://AngularjsSiteUrl" />
        <add name="Access-Control-Allow-Headers" value="Content-Type,Accept,X-FORMS_BASED_AUTH_ACCEPTED,crossDomain,credentials " />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
        <add name="Access-Control-Allow-Credentials" value="true" />
      </customHeaders>
</httpProtocol>

The above changes will help to fix the issue and the Ajax request will now execute successfully.