First published on TECHNET on Jun 10, 2017
Updated on 6/4/2020 to fix an error
This post is a contribution from Jing Wang, an engineer with the SharePoint Developer Support team
Symptom:
A 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"
},
success: function(response) {
alert("Success");
},
error: function(response){
alert("Error" );
}
});
</script>
</body>
</html>
When using Chrome (this also will effect Edge, Firefox or Safari) 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:
SharePoint only supports cross-site calls (CORS) for SharePoint add-ins. Since SharePoint doesn't return the headers and responses needed, Chrome blocks the cross-site request. While this is not supported, if you want to make a cross-site call to SharePoint, you can enable it by following the steps below.
When an XMLHttpRequest is sent with added custom headers, like,
headers.append('Content-Type', 'application/json;odata=verbose');
headers.append('credentials', 'include');
these custom headers make 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.
- The browser (Chrome) sends a preflight OPTIONS request to SharePoint WFE server, which hosts the listdata.svc, without credential first (anonymous)
- The server returns an 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.
A Fiddler trace shows:
Solution:
Step I ,
Set IIS (on the SharePoint 2013 WFE Server) to send Http Status code of 200 for the preflight requests by using 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, the above change is not enough to make the XMLHttpRequest call go through.
With the changes in Step I, the 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 add some custom headers required to make a CORS request:
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. In the below XML, where it has <your_origin> you would put the host of your remote application that is calling to SharePoint (e.g. https://myawesomeapp.net)-
----
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="<your_origin>" />
<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 allow your application to make cross domain calls to SharePoint 2013 using AJAX requests successfully.
In SharePoint 2016 the above will not work, and all CORS requests are blocked unless made with OAuth permissions (using Azure AD applications or the SharePoint Add-in model).
There is a fix that takes care of the blocked options requests, but you cannot use URL re-write or the IIS headers to fake support for CORS as above. The fix that enables the options CORS pre-flight is here-
This security update contains improvements and fixes for the following nonsecurity issues for SharePoint Server 2016:
- When a web browser makes a cross-origin resource sharing (CORS) request to a SharePoint REST API, the browser typically sends an OPTIONS preflight request to SharePoint without authentication. SharePoint returns an HTTP 401 status code response for this preflight request, which is not correct.
With this update, SharePoint introduces the option to respond to the CORS request by sending an HTTP 200 status code, which is the correct behavior. You must run the following commands in PowerShell to enable the new behavior:
$stsConfig = Get-SPSecurityTokenServiceConfig
$stsConfig.ActivateOkResponseToCORSOptions = $true
$stsConfig.Update();
Again, this will not change the fact that CORS calls now require OAuth tokens to work.
If you want to subvert the CORS support or add headers to SharePoint responses, you might be able to with external proxies, or an HTTP Module, but this puts the security mitigation on your code, make sure to secure it, we don't provide guidance for it since it is unsupported.
More information on the SharePoint Add-in model here-
https://docs.microsoft.com/en-us/sharepoint/dev/sp-add-ins/sharepoint-add-ins
Welcome to the SharePoint Blog! Learn best practices, news, and trends directly from the SharePoint team.