Wednesday, November 23, 2016

How to make Chrome support SSO, and enable CORS

Recently I migrated some SharePoint web parts from C# to JavaScript + HTML

Everything works well in IE 11 after enabling CORS.

But, when test it in Chrome 54, I got the error message below, and it constantly prompt for user name and password..

ERR_INVALID_HANDLE: "This site can’t be reached"

IIS log says it's requested by anonymous user.

After days of struggling, it turns out not as easy as it looks like.  We need to do the following steps.

1. IE -> internet options -> security -> Local Intranet zone

Add SharePoint Server and the Web App Server to "Local Intranet zone". So IE and Chrome will try to use the current windows user credential to log on web server.

This enables NTLM authentication on SharePoint and Web App Server.

Reference: https://sysadminspot.com/windows/google-chrome-and-ntlm-auto-logon-using-windows-authentication/

2. Configure Delegated Security in Google Chrome

Need to add server names as below to registry table on client computer.

We can do it through Group Policy.

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome]
"AuthNegotiateDelegateWhitelist"="*.DomainName.local"
"AuthSchemes"="digest,ntlm,negotiate"
"AuthServerWhitelist"="*.DomainName.local"

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Policies\Google\Chrome]
"AuthNegotiateDelegateWhitelist"="*.DomainName.local"
"AuthSchemes"="digest,ntlm,negotiate"
"AuthServerWhitelist"="*.DomainName.local"

This enables NTLM authentication and Kerberos on SharePoint and Web App Server.

Reference: https://specopssoft.com/configuring-chrome-and-firefox-for-windows-integrated-authentication/

3. Configure Kerberos

Set up SPN for both the SharePoint Server and the Web App Server.

Reference: https://support.microsoft.com/en-au/kb/929650

4. Change Startup.cs a bit in Configure() to handle preflight requests

This is for CORS.

Reference: http://stackoverflow.com/questions/38838006/asp-net-mvc-core-cors

5. Enable Anonymous Authentication on Web App Server

This is for CORS.

6. If Kestrel Server is not running, we need to submit "GET" request first.

It cannot be started up by "Preflight Options" request. It seems like a bug.

[update 2016-11-29] 7. To make things easier, add the settings below to the web.config file of Web App Server. 

This helps to enable CORS.

<httpProtocol>
  <customHeaders>
<clear />
<add name="Access-Control-Allow-Origin" value="http://SharePointSiteUrl" />
<add name="Access-Control-Allow-Headers" value="Authorization, X-Requested-With, Content-Type, Origin, Accept, X-Auth-Token" />
<add name="Access-Control-Allow-Methods" value="*" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Max-Age" value="60" />
  </customHeaders>
</httpProtocol>


Done.

=================

Test Environment.

Client side: Chrome 54 + JavaScript + JQuery 3.1.1

Server side: SharePoint Server 2016 CU201611 + Content Editor Web Part

Web App Server: IIS 8.5 + Asp.Net Core Web API 2 + C#

JavaScript + JQuery 3.1.1 (based on JSON, in a Content Editor Web Part)

var strUrl = "http://WebService2016dev.DomainName.local/SSO/AppTest1/api/SSOAuthentication";

var JSONObject= {"Key": "db25f36b-fa81-4c8e-9af5-9c8468ce8a79",
   "UserLoginName": "domain\\UserLoginName",
   "ReturnCode": "",
   "ReturnValue": "",
   "ReturnDetailedInfo": "" };
var jsonData = JSON.stringify(JSONObject);

$.support.cors = true;

var strMethodType = 'GET';
var strContentType = 'text/plain';

$.ajax( {
url: strUrl,
type: strMethodType,
contentType: strContentType ,
xhrFields: {
withCredentials: true
},
data: '',
dataType: "json",
async: false,
crossDomain: true,
success: function( response ) {
console.log("GET success - Data from Server: " + JSON.stringify(response));
},
error: function( request, textStatus, errorThrown ) {
console.log("GET error - You can not send Cross Domain AJAX requests: textStatus=" + textStatus + ", errorThrown=" + errorThrown);
console.log(request.responseText);
}
} );

var strMethodType = 'POST';
var strContentType = 'application/json; charset=utf-8';

$.ajax( {
url: strUrl,
type: strMethodType,
contentType: strContentType ,
xhrFields: {
withCredentials: true
},
data: jsonData,
dataType: "json",
crossDomain: true,
success: function( response ) {
console.log("POST success - Data from Server: " + JSON.stringify(response));
},
error: function( request, textStatus, errorThrown ) {
console.log("POST error - You can not send Cross Domain AJAX requests: textStatus=" + textStatus + ", errorThrown=" + errorThrown);
console.log(request.responseText);
}
} );

Web App Server (IIS 8.5 + Asp.Net Core Web API 2), main C# code in Startup.cs

app.Use(async (httpContext, next) =>
{
await next();
if (httpContext.Request.Path.Value.Contains(@"api/") && httpContext.Request.Method == "OPTIONS")
{
httpContext.Response.StatusCode = StatusCodes.Status204NoContent;
}
});

app.UseMvc();

No comments:

Post a Comment