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="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js" type="text/javascript" ></script> <script src="http://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 = "http://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.
- Browser (Chrome) sent preflight OPTIONS request to SharePoint WFE server, which hosts the listdata.svc, without credential first,
- Server returned HTTP/1.1 401 Unauthorized response for the preflight request.
- 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:
- Install "web platform installer" from https://www.microsoft.com/web/downloads/platform.aspx
- Go to "Applications" tab and search for "URL Rewrite" and download it
- 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.
- Add a new blankrule by clicking on Add Rule --> New Blank Rule from the menu on the right
- Give it any name
- In "Match URL", specify this pattern: .*
- In "Conditions" click on Add and specify this condition entry: {REQUEST_METHOD} and this pattern: ^OPTIONS$
- In "Action", specify: action type Personalized response (or Customized reponse), state code 200, reason Preflight, description Preflight
- 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 http://***/_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="http://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.