SharePoint 2013 Forms Based Authentication is Slow– Why does SetPrincipalAndWriteSessionToken take 20 seconds or more? - sharepoint

We have a SharePoint implementation in which our web application is using Forms Based Authentication(FBA).
There are 2 servers in the farm. A web front end server that resides in a DMZ and a SQL server within the corporate network. A firewall separates them.
We are using SQL Authentication.
We need to force the user to change their password after the first successful login. Therefore we created a custom signin form for FBA based on the following article.
(https://sharepoint.stackexchange.com/questions/42541/how-to-create-a-custom-fba-login-page-that-forces-user-to-change-password-and-vi).
The code in question is:
private void SignInUser()
{
SecurityToken token = SPSecurityContext.SecurityTokenForFormsAuthentication
(new Uri(SPContext.Current.Web.Url),
GetMembershipProvider(SPContext.Current.Site),
GetRoleProvider(SPContext.Current.Site),
_userName,
_password, SPFormsAuthenticationOption.None);
SPFederationAuthenticationModule fam = SPFederationAuthenticationModule.Current;
fam.SetPrincipalAndWriteSessionToken(token, SPSessionTokenWriteType.WriteSessionCookie);
SPUtility.Redirect(System.Web.Security.FormsAuthentication.DefaultUrl,
SPRedirectFlags.UseSource, this.Context);
}
public static string GetMembershipProvider(SPSite site)
{
// get membership provider of whichever zone in the web app fba isenabled
SPIisSettings settings = GetFbaIisSettings(site);
if (settings == null) return null;
return settings.FormsClaimsAuthenticationProvider.MembershipProvider;
}
public static string GetMembershipProvider(SPSite site)
{
// get membership provider of whichever zone in the web app is fba enabled
SPIisSettings settings = GetFbaIisSettings(site);
if (settings == null) return null;
return settings.FormsClaimsAuthenticationProvider.MembershipProvider;
}
The code which takes the time is:
fam.SetPrincipalAndWriteSessionToken(token, SPSessionTokenWriteType.WriteSessionCookie);
From my understanding this line of code does the following:
Invokes the OnSessionSecurityTokenCreated method to raise the
SessionSecurityTokenCreated event
Invokes the AuthenticateSessionSecurityToken method on SPFederationAuthenticationModule.Current to set the thread principal and then write the session cookie.
Some other points to note are:
This 20 second login time also occurs for the default sharepoint fba page (/_forms/default.aspx)
It does not occur on a standalone dev machine.
For me this would indicate the bottleneck is network related.
Any help would be much appreciated.

I managed to shave about 13 seconds off the login process by resolving the following ULS log entry.
3/30/2016 11:08:53.71 w3wp.exe (0x2448) 0x1148 SharePoint Foundation Topology 8321 Critical A certificate validation operation took 23141.9482 milliseconds and has exceeded the execution time threshold. If this continues to occur, it may represent a configuration issue. Please see http://go.microsoft.com/fwlink/?LinkId=246987 for more details. bc926d9d-52af-f0fb-b2ae-236a27cd54f1
So, SharePoint uses certificates to sign security tokens that are issued by the Security Token Service (STS). Like all certificates, the validity of the STS certificate has to be verified periodically to make sure that the certificate has not been revoked. By default, the root certificate in the chain is not added to the Trusted Root Certificate Authorities store of the SharePoint servers. Because of this, the certificate revocation list (CRL) check for the certificate is performed over the Internet which is not possible on our WFE server.
I resolved this by exporting the root cert, on the WFE server, using
$rootCert = (Get-SPCertificateAuthority).RootCertificate
$rootCert.Export("Cert") | Set-Content C:\SharePointRootAuthority.cer -Encoding byte
And then importing the cert into the Trusted Root Certification Authorities store using the certificates mmc snapin.

Related

High Trust S2S Provider Hosted App with "App + User" Policy

I am relatively new to sharepoint app development.
Trying to create a on premises, High Trust provider hosted app with App + User Policy. I have followed below document to create a demo.
https://msdn.microsoft.com/library/office/fp179901(v=office.15)
http://blogs.msdn.com/b/russmax/archive/2014/06/23/part-1-intro-to-provider-hosted-apps-setup-the-infrastructure.aspx
I am facing few issue and I have some question to clarify, if anybody can help.
1) When I inspect my request in dev tools, it give me below form data.
SPAppToken:
SPSiteUrl:
SPSiteTitle:Home
SPSiteLogoUrl:
SPSiteLanguage:en-US
SPSiteCulture:en-US
SPRedirectMessage:EndpointAuthorityMatches
SPErrorCorrelationId:f069e89c-a0cd-20ce-a1c0-7db95db0334b
now when i inspect log with above corelation id, i am finding below errors.
-- Error when get token for app i:0i.t|ms.sp.ext|ab8ff461-bc75-4516-b475-b666ac47eec0#802f23e1-6e11-45d1-909c-07a7b0ab0ce2,
exception: Microsoft.SharePoint.SPException: The Azure Access Control
service is unavailable.
-- App token requested from appredirect.aspx for site: 92bfe5c4-7255-4b09-a89a-07e0e2b03622 but there was an error in
generating it. This may be a case when we do not need a token or when
the app principal was not properly set up.
-- Getting Error Message for Exception Microsoft.SharePoint.SPException: The Azure Access Control service is
unavailable.
a) I belive in high-trust app it shouldn't look for Azure ACS.
Is this error because of some incorrect configuration?
b) SPAppToken is null here. Is it null always in case of hig trust app?
2) Say I am logged into sharepoint with User A and trying to launch sharepoint app.
Within app code I want to get identity of logged in user(which is A). From below code i found that Request.LogonUserIdentity gives me identity of user A. But how can we sure that request is came from sharepoint only. I can copy the same app URL and paste in browser window and login with window credential and get the same result. So question is how can I verify if its legitimate request came from sharepoint only and no one is faking request.
ALos, when I inspect request in dev tools, its passing Authorization key in request header. What is use of this?
using (var clientContext = TokenHelper.GetS2SClientContextWithWindowsIdentity(hostWeb, Request.LogonUserIdentity)) { clientContext.Load(clientContext.Web, web => web.Title); clientContext.ExecuteQuery(); Response.Write(clientContext.Web.Title); }
3) Also what happens if my app doesnt support windows authentication and only support FBA, is there any way to get user identity in this case?
Any help would be much appreciated.
Thanks
For issue #1: It looks to me that the step # 9 (Configure authentication settings) in this section (from the first MSDN article you have referred) was missed, i.e., 'ACS Control service' was selected instead of 'Use a Certificate' option.
For issue #2: There are helper methods in TokenHelper.cs to validate the AccessToken from the HttpRequest, which identifies the validity of the request.

Will a Windows Store app always disallow a self-signed certificate even if explicitly trusted?

I've seen both this and this — same problem, different question.
I'm trying to connect my Windows 8.1 Store app to an ASP.NET Web API web service, secured over HTTPS using a self-signed certificate. It's a proof-of-concept application that will end up on < 5 different machines and seen only internally, so I was planning to just install the certificate as trusted on each of the target machines.
When I try this on my development setup, both HttpClient APIs fail to establish the trust relationship when calling the service.
Windows.Web.Http.HttpClient exception: "The certificate authority is invalid or incorrect"
System.Net.Http.HttpClient exception: "The remote certificate is invalid according to the validation procedure."
My self-signed certificate (public-key-only .cer version) is installed in both the "User" and "Local Machine" Trusted Root Certification Authorities on the client. I'm really surprised that this isn't enough to get WinRT to trust it. Is there something I'm missing, or is there just no way to set up the trust relationship for a self-signed SSL certificate that will make HttpClient happy?
Details on my setup:
ASP.NET Web API
Azure web role running in Azure emulator
Cert issuer: 127.0.0.1
Cert subject: 127.0.0.1
Cert key: 2048-bit
Windows 8.1 Store application
Certificate (.cer file with public key only) installed in User\Trusted Root Certification Authorities
Certificate (.cer file with public key only) installed in Local Machine\Trusted Root Certification Authorities
Certificate (.cer file with public key only) added to Windows Store app manifest under "CA"
I am not asking for a workaround to configure HttpClient to accept self-signed or invalid certificates in general — I just want to configure a trust relationship with THIS one. Is this possible?
You should be able to find out what is the problem with the certificate by doing a request like this:
// using Windows.Web.Http;
private async void Foo()
{
HttpRequestMessage request = null;
try
{
request = new HttpRequestMessage(
HttpMethod.Get,
new Uri("https://localhost"));
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.SendRequestAsync(request);
}
catch (Exception ex)
{
// Something like: 'Untrusted, InvalidName, RevocationFailure'
Debug.WriteLine(String.Join(
", ",
request.TransportInformation.ServerCertificateErrors));
}
}
Using a HttpBaseProtocolFilter you can ignore certificate errors:
// using Windows.Web.Http;
// using Windows.Web.Http.Filters;
// using Windows.Security.Cryptography.Certificates;;
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationFailure);
HttpClient client = new HttpClient(filter);
HttpResponseMessage response = await client.SendRequestAsync(request);
The piece I was missing turned out to be that the certificate wasn't in the list of of IIS Server Certificates on my local machine!
Opening IIS Manager and checking out the Server Certificates section, I did find a 127.0.0.1 SSL certificate already set up by the Azure emulator:
CN = 127.0.0.1
O = TESTING ONLY
OU = Windows Azure DevFabric
However, my own self-signed certificate that I made outside of IIS, also with CN=127.0.0.1, was not in the list. I imported it, and now my Windows Store app's HttpClient connects happily (certificate warnings went away in Chrome and IE as well!)
If anyone can firm up the technical details on this, please comment — this fix feels a bit magical and I'm not sure I can pinpoint precisely why this worked. Possibly some confusion on my part between the two certs for 127.0.0.1, even though the thumbprint I had configured in my Azure project was always the one I was intending to use?

HttpRequest with Certificate fails in Azure Web-role

On my deployed azure web-role I try to send a request (GET) to a Web-Server that authorizes the request by the provided certificate of the requesting client.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
var filepath = Path.GetTempPath();
string certpath = Path.Combine(filepath, "somecert.cer");
Trc.Information(string.Format("Certificate at {0} will be used", certpath));
X509Certificate cert = X509Certificate.CreateFromCertFile(certpath);
WebRequest request = WebRequest.Create(endPoint);
((HttpWebRequest)request).ProtocolVersion = HttpVersion.Version10;
((HttpWebRequest)request).IfModifiedSince = DateTime.Now;
((HttpWebRequest)request).AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
((HttpWebRequest)request).ClientCertificates.Add(cert);
The above code works perfectly in the azure-emulator but not when it is deployed. Then the call to GetResponse fails always.
System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
at System.Net.HttpWebRequest.GetResponse()
at XYZ.Import.DataImport.OpenResponseStream(String endPoint)
I read through many of the existing discussion threads where using SecurityProtocolType.Ssl3 solved the problem but it does not in my case. Are there further debugging options considering that it is running on azure?
Update1
I tried all debugging steps that were suggested by Alexey. They are really helpfull but quite hard to execute properly on azure.
Here is with what I came up with after at least two hours.
I used the System.Net settings supplied by this post [1].
At first the output was not present in the expected folder. The file system settings on the folder need to be tweaked. Therefore the NT AUTHORITY\NETWORK SERVICE account should be allowed on the target folder.
After that the file didn't show up as expected because there seems to be a problem when only a app.config is supplied. See this thread [2]. So I provided a app.config a [ProjectAssembly].dll.config and a web.config with the content from the post [1].
To test if the Problem is related to User rights I tested with elevated rights and without like shown in post [3].
In advance I changed the Test-Project to execute in two modes. The first mode tries to load the public part in the *.cer file like shown in the code above.
The other version uses the private certificate that is loaded with this command
X509Certificate cert = new X509Certificate2(certpath, "MYPASSWORD", X509KeyStorageFlags.MachineKeySet);
As a result I gained the following insights.
When using the public part (.cer) it only works when the rights are elevated and the private cert is imported into the machine store
When using the private (.pfx) it only works if the private cert is imported into the machine store
The second setup with (.pfx) runs even without elevated rights
While debugging the CAPI2 log only had informations that had no direct relevance. The System.Net diagnostics from point one above contained this.
System.Net Information: 0 : [1756] SecureChannel#50346327 - Cannot find the certificate in either the LocalMachine store or the CurrentUser store.
[snip]
System.Net Error: 0 : [1756] Exception in HttpWebRequest#36963566:: - The request was aborted: Could not create SSL/TLS secure channel..
System.Net Error: 0 : [1756] Exception in HttpWebRequest#36963566::GetResponse - The request was aborted: Could not create SSL/TLS secure channel..
From this output and the changing situation when the elevated rights are used I would deduce that I should look further into the rights of the running web-role in combination with the certificate store.
[1] http://msdn.microsoft.com/de-de/library/ty48b824(v=vs.110).aspx
[2] Combined Azure web role and worker role project not seeing app.config when deployed
[3] http://blogs.msdn.com/b/farida/archive/2012/05/01/run-the-azure-worker-role-in-elevated-mode-to-register-httplistener.aspx
Remove SecurityProtocolType.Ssl3
Turn on CAPI2 log and check it for errors (on your local machine).
If there isn't error, then check location of CA and intermediate certificates.
Turn on system.net diagnostics and check this log for errors.
In this article describes how to find and turn on CAPI2 eventlog.
Hope this help.

Certificates List is empty in WebRole

I am writing a code in my web app which needs to list and search for a specific Certificate installed on web role.
Here is my code
// using System.Security.Cryptography.X509Certificates;
var store = new X509Store() ;
store.Open(OpenFlags.ReadOnly);
LoggingService.Info(String.Format(
"{0} Certificate(s) are found in store",store.Certificates.Count));
for(int index=0;index<store.Certificates.Count;index++)
{
LoggingService.Info(String.Format(
"Subject:{0}, Thumbprint:{1}",store.Certificates[index].Subject,
store.Certificates[index].Thumbprint));
}
_Certificate = store.Certificates.Find(
X509FindType.FindByThumbprint, this.CertificateThumbprint, false)[0];
Now Problem is, even though a certificate is added in the web role through portal and also present in the config file. store.Certificates.Count is zero. This code runs perfectly in emulator but somehow is unable to list the web role certificates. How can I access the certificate installed on web role?
Got it, I was not providing any store name and location in Store class and was assuming it will search in every store and location but it was not like this. I then provided the Store name and location and now system is able to find certificate.

Unable to authenticate to ASP.NET Web Api service with HttpClient

I have an ASP.NET Web API service that runs on a web server with Windows Authentication enabled.
I have a client site built on MVC4 that runs in a different site on the same web server that uses the HttpClient to pull data from the service. This client site runs with identity impersonation enabled and also uses windows authentication.
The web server is Windows Server 2008 R2 with IIS 7.5.
The challenge I am having is getting the HttpClient to pass the current windows user as part of its authentication process. I have configured the HttpClient in this manner:
var clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
clientHandler.PreAuthenticate = true;
clientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var httpClient = new HttpClient(clientHandler);
My understanding is that running the site with identity impersonation enabled and then building the client in this manner should result in the client authenticating to the service using the impersonated identity of the currently logged in user.
This is not happening. In fact, the client doesn't seem to be authenticating at all.
The service is configured to use windows authentication and this seems to work perfectly. I can go to http://server/api/shippers in my web browser and be prompted for windows authentication, once entered I receive the data requested.
In the IIS logs I see the API requests being received with no authentication and receiving a 401 challenge response.
Documentation on this one seems to be sparse.
I need some insight into what could be wrong or another way to use windows authentication with this application.
Thank You,
Craig
I have investigated the source code of HttpClientHandler (the latest version I was able to get my hands on) and this is what can be found in SendAsync method:
// BeginGetResponse/BeginGetRequestStream have a lot of setup work to do before becoming async
// (proxy, dns, connection pooling, etc). Run these on a separate thread.
// Do not provide a cancellation token; if this helper task could be canceled before starting then
// nobody would complete the tcs.
Task.Factory.StartNew(startRequest, state);
Now if you check within your code the value of SecurityContext.IsWindowsIdentityFlowSuppressed() you will most probably get true. In result the StartRequest method is executed in new thread with the credentials of the asp.net process (not the credentials of the impersonated user).
There are two possible ways out of this. If you have access to yours server aspnet_config.config, you should set following settings (setting those in web.config seems to have no effect):
<legacyImpersonationPolicy enabled="false"/>
<alwaysFlowImpersonationPolicy enabled="true"/>
If you can't change the aspnet_config.config you will have to create your own HttpClientHandler to support this scenario.
UPDATE REGARDING THE USAGE OF FQDN
The issue you have hit here is a feature in Windows that is designed to protect against "reflection attacks". To work around this you need to whitelist the domain you are trying to access on the machine that is trying to access the server. Follow below steps:
Go to Start --> Run --> regedit
Locate HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0 registry key.
Right-click on it, choose New and then Multi-String Value.
Type BackConnectionHostNames (ENTER).
Right-click just created value and choose Modify.
Put the host name(s) for the site(s) that are on the local computer in the value box and click OK (each host name/FQDN needs to be on it's own line, no wildcards, the name must be exact match).
Save everything and restart the machine
You can read full KB article regarding the issue here.
I was also having this same problem. Thanks to the research done by #tpeczek, I developed the following solution: instead of using the HttpClient (which creates threads and sends requests async,) I used the WebClient class which issues requests on the same thread. Doing so enables me to pass on the user's identity to WebAPI from another ASP.NET application.
The obvious downside is that this will not work async.
var wi = (WindowsIdentity)HttpContext.User.Identity;
var wic = wi.Impersonate();
try
{
var data = JsonConvert.SerializeObject(new
{
Property1 = 1,
Property2 = "blah"
});
using (var client = new WebClient { UseDefaultCredentials = true })
{
client.Headers.Add(HttpRequestHeader.ContentType, "application/json; charset=utf-8");
client.UploadData("http://url/api/controller", "POST", Encoding.UTF8.GetBytes(data));
}
}
catch (Exception exc)
{
// handle exception
}
finally
{
wic.Undo();
}
Note: Requires NuGet package: Newtonsoft.Json, which is the same JSON serializer WebAPI uses.
The reason why this is not working is because you need double hop authentication.
The first hop is the web server, getting impersonation with Windows authentication to work there is no problem. But when using HttpClient or WebClient to authenticate you to another server, the web server needs to run on an account that has permission to do the necessary delegation.
See the following for more details:
http://blogs.technet.com/b/askds/archive/2008/06/13/understanding-kerberos-double-hop.aspx
Fix using the "setspn" command:
http://www.phishthis.com/2009/10/24/how-to-configure-ad-sql-and-iis-for-two-hop-kerberos-authentication-2/
(You will need sufficient access rights to perform these operations.)
Just consider what would happen if any server was allowed to forward your credentials as it pleases... To avoid this security issue, the domain controller needs to know which accounts are allowed to perform the delegation.
To impersonate the original (authenticated) user, use the following configuration in the Web.config file:
<authentication mode="Windows" />
<identity impersonate="true" />
With this configuration, ASP.NET always impersonates the authenticated user, and all resource access is performed using the authenticated user's security context.

Resources