Blog Post

Microsoft SharePoint Blog
5 MIN READ

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

SPDev_Support's avatar
SPDev_Support
Icon for Microsoft rankMicrosoft
May 01, 2019

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',
withCredentials: true,
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.

    1. The browser (Chrome) sends a preflight OPTIONS request to SharePoint WFE server, which hosts the listdata.svc, without credential first (anonymous)

    2. The server returns an 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.


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:

    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, 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-

https://support.microsoft.com/en-us/help/4032256/description-of-the-security-update-for-sharepoint-enterprise-server

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

 

Updated Apr 30, 2021
Version 9.0
  • Hello Gabriel,

    provider hosted apps should be using PHA authentication (low trust/high trust) which is independent of Windows auth.

    If you decide to get your authentication a different way you can, but you might run into unique issues that other people will never see using PHA auth.

    The app proxy problem depends on which call is failing, you should figure that out first by using a tracing proxy (like fiddler) or logging added to your PHA to see if it is the call thru the proxy failing. In the PHA, all calls out will have to have something added to them for the proxy url and credentials so it can provide what is needed there.

    Without a trace of the exact call pattern and which one is failing, it isn't possible to troubleshoot this issue.

     

     

  • This doesn't work in 2016 except when you use OAuth using the application authentication model, by design.

    SharePoint doesn't support allowing other locations to access data cross-origin without authenticating.

    If you wanted to do something like this you could create an HTTP module that could handle the responses to enable the functionality. 

  • manojkgaai's avatar
    manojkgaai
    Copper Contributor

    SPDev_Support,

     

    We have similar situation with our SP2016 deployment, business users want to call SP2016 sites from other application using JavaScript. IE browser still supports CORS call happen. But modern browsers such as Chrome, Edge blocks CORS requests.

    So i followed below steps, but still I am facing CORS issue. (Access to XMLHttpRequest at 'http://***********/_api/web/lists/getByTitle('Documents')/items?$select=FileLeafRef,EncodedAbsUrl' from origin 'http://localhost:3007' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.)

     

    1. Installed this SharePoint Patch https://support.microsoft.com/en-us/help/4032256/description-of-the-security-update-for-sharepoint-enterprise-server and Activated OkResponseToCORSOptions

    2. Integrated On-premises SharePoint 2016 with Azure Active Directory as here. https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/sharepoint-on-premises-tutorial#configuring-one-trusted-identity-provider-for-multiple-web-applications

    3. Registered Azure AD App Registration with SharePoint On-Premises API permissions.

    4. Created a simple NodeJS application as here. https://docs.microsoft.com/en-us/learn/modules/identity-application-types/3-exercise-single-page-applications. I am able to call SharePoint Online API successfully. But On-Prem calls are still failing with CORS error.

    3. MSAL Library is returning Access Token.

     

    Please let know what did i do wrong here.

  • You MUST use a app token to make a CORS request. Without it SP 2016+ will block CORS calls, as per the above article.

  • I have a SharePoint Subscription Edition farm. I cannot set ActivateOkResponseToCORSOptions , the property is missing. Same with SharePoint 2019. What am I missing here? Are there any prerequisites needed for this? Not a lot of information on this and I would assume since it was introduced for 2016, it would carry on to the newer versions?

  • I believe that that is not needed with that version of SharePoint. You just need to use application/OAuth on your request.

  • I get this error in my provider hosted app, installed on a Subscription Edition farm. The app uses Windows authentication to get an access token from SharePoint, this is then used as auth header in another AJAX request to SharePoint. Works fine when accessed directly. But when both SharePoint and my app is behind a AD FS Web Application Proxy with pre-auth I get this error. I assume it is because the WAP redirects the preflight request, violating the CORS policy. Is there a solution?

  • Hello Westley,

     

    Thanks for your reply. To clarify, the app is only using Windows Auth to make sure that there is a Windows identity that can be used to get an access token from SharePoint via the app's backend. This access token is then used with AJAX requests to SharePoint.

     

    The call that is failing is that AJAX call to the SharePoint REST API. It has the token in the headers, but is redirected to ADFS, which itself is also behind WAP. This POST request, triggers a preflight OPTIONS request, which is redirected to ADFS, which is not allowed for CORS requests.

     

    I have tried enabling CORS in ADFS on the ADFS server, but the behavior is the same. The AJAX request is not logged in the event viewer on the WAP server while other requests are, without errors. So to me it seems that the web browser is seeing that WAP wants to redirect the preflight request to SharePoint and terminates as it does not match the CORS policy. Does ADFS CORS configuration work with WAP?

  • The problem sounds like is that the WAP is redirecting a request with a valid token, this shouldn't happen.

     

    CORS on ADFS is if you want to make CORS calls to ADFS. This doesn't sound like what you want. Once the session has been authenticated, you shouldn't need to contact the ADFS server as long as the cookie or token is there.

     

    I do have a question about the Windows auth you mentioned, are you talking about the legacy Windows auth with NTLM where there is no token or cookie involved? Or is this claims Windows where you get a fedauth token? Are you using the S2S token helper method with the Windows identity to get the token?

     

    If you have to authenticate against the WAP from JS code running in the browser, that would mean you would have to write the code to maybe ask for a page first to handle the redirects for the WAP auth. Then once that is all working, then make the real AJAX calls to get the SharePoint data.

     

    This wouldn't be an issue if the calls aren't done from the browser since you can set up the proxy on the call in C# code, but you can't do that from the browser.

  • We are not using NTLM. Both SharePoint and app is configured to use Windows Auth with Negotiate/Kerberos.

     

    Yes, we are using the S2S token helper with a WindowsIdentity object to get an access token from SharePoint that can be used from Javascript for file uploads. Most requests are proxied through our server side code but file uploads we would rather send directly to SharePoint, which is a CORS request. 

     

    I don't get prompted to sign in to SharePoint if I open the app first, and vice verca if that helps.