I have a Web Part where I am trying to call a WCF Service as the current logged in user. The Web Part work locally when debugging in Visual Studio 2013 - calling the external WCF service. I have configured Kerberos for the WCF Service as well as the SharePoint site and fee like that is all correct. I can call the WCF Service correctly from another Kerberos enabled web app fine as well as from a console app.
Now I'd like to roll this to a SharePoint farm. However after I add the solution to the farm and activate the feature on the sharepoint site...I get an error when I try to add the web part to a page. The error comes up in a box that says "The caller was not authenticated by the service.". The error comes from the actualy method call.
My web part code is as such:
WorksiteService.iWorksiteServiceClient wss = new WorksiteService.iWorksiteServiceClient("WSHttpBinding_iWorksiteService");
wss.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
wss.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
WorkSiteDocument[] wd = wss.GetDocumentsByAlias2("test");
The web.config in for the sharepoint site has this for the endpoint:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_iWorksiteService" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://myservicerd.xyz.com/WorkSiteService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_iWorksiteService" contract="WorksiteService.iWorksiteService" name="WSHttpBinding_iWorksiteService">
<identity>
<servicePrincipalName value="myservicerd.xyz.com" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Any ideas on why I get that error? I'm assuming I'm not getting the current user's credentials for some reason.There is nothing in the event or sharepoint logs of the sharepoint server or wcf server. Any ideas on how I can troubleshoot this?Thank you for any assistance you can provide.
I'm not a Sharepoint Expert, but from a Kerberos perspective I would suggest to check the delegation settings in Active Directory just as Marek suggested.
In a double-hop scenario like this, the 'man' in the middle must be permitted to take on the identity of the client.
The question remains who the 'man' in the middle is, which depends on the IIS configuration.
If the IIS is configured for kernel-mode authentication the middle identity is probably the computer account if the server IIS is installed on.
Otherwise I would assume that the identity that needs delegation rights should be the identity of the application pool the web part runs in.
If you determined the identity, go to the appropriate Active Directory account (delegation tab) and select 'Account is trusted for delegation'.
Maybe this blog article can shed some light on this: http://tekaris.com/blog/2013/04/05/http-400-bad-request-kerberos-authentication-with-iis-2/
Related
My testing environment is:
1* Server :: SharePoint Site with WCF Service (in the ISAPI folder) and we configured the SSL and Cert on it.
1* Client :: Windows 10 with one Console Application to involve the WCF service
Testing (1) :all programs (WCF Service and Console Application) on the SharePoint Server
I created a Console application to invoke the WCF Service in SharePoint. Meantime, the Console application and WCF are on the same server. the outcome is everything works properly. It is successful to upload file into a Document Library.
Testing (2) :to simulate my client environment:
1* SharePoint :WCF Service
1* Windows 10 :Console application
the outcome failed and I got the error message:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'NTLM'
To be honest, I stuck here for a while. I also list out a portion of my source code for someone of experience to look at. I also posted some sample code and web.config in here.
Updated.
If we enable NTLM authentication, we have to enable the windows authentication.
Then when calling the service, we need to provide windows credential.
//it will use the binding and service endpoint address in the system.servicemode section.
ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
//windows account on the server.
client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = "abcd1234!";
try
{
Console.WriteLine(client.SayHello());
}
catch (Exception)
{
throw;
}
Auto-generated configuration.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService">
<security mode="Transport">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://vabqia969vm:21011/" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService" contract="ServiceReference1.IService"
name="WSHttpBinding_IService">
<identity>
<userPrincipalName value="VABQIA969VM\Administrator" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Feel free to let me know if the problem still exists.
There was no endpoint listening at http://api/APIWCF.svc/soap that could accept the message. This is often caused by an incorrect address or SOAP action
I've created a new website in IIS called API, I assigned it to the folder of where my application is. But when I run it, it gives me error mentioned above.
What I've tried:
Deleted the website and re-added it in IIS.
I set my project to run under local IIS under web properties.
It's running under the current page.
I changed the virtual directory.
I checked the webconfig folder, which is as follows:
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/>
Have I done something wrong in IIS?
I cant figure it out.
change the Url in the client endpoint to have the host name
<endpoint address="http://[your host name]api/APIWCF.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_APIWCF"
contract="Bettingworld.JuiceAPIWCF"
name="BasicHttpBinding_APIWCF"/>
</client>
We use a WCF service (hosted on IIS, as part of a C# Silverlight project, using Windows Authentication to log in).
I want to start a thread to do time consuming processing, and it should periodically update the database. With "time consuming" I mean at least a couple of minutes, but always less than an hour. I don't want to risk the async server call timing out.
The problem is that we use Windows Authentication, and once the process enters the thread it appears to lose context and fails with the following error:
Login failed for user 'IIS APPPOOL\DefaultAppPool'.
I tried using a BackgroundWorker to move the DB code to the ProgressChanged() and RunWorkerCompleted() calls, in a hope that they might join to the original context, but without luck.
In your IIS, make a right click on the application pool --> Advanced settings and change the application pool user to a windows user that has access to the database.
An application running on IIS always runs under the user you define there. Therefor your WCF service won't have access to the database because at the moment your IIS Pool uses the user "APPPOOL\DefaultAppPool" that is a local user.
While you can configure the application pool to run as a specific domain user account which has access to the database, keep in mind that the IIS service process will now be running as that user. Optionally, you can pull the client service credentials and using a WindowsImpersonationContext, establish the database connection using the client credentials.
In your service, you could do something like this:
//open a SQL Server database connection using the client credentials
using (var impcontext = ServiceSecurityContext.Current.WindowsIdentity.Impersonate())
{
var dbConnection = new System.Data.SqlClient.SqlConnection("Data Source=myServer; Integrated Security=SSPI");
dbConnection.Open();
impcontext.Undo();
}
This assumes that your WCF service is setup as follows:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicHttpBindingConfiguration">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WcfService1.Service1">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicHttpBindingConfiguration" contract="WcfService1.IService1" />
</service>
</services>
<behaviors/>
</system.serviceModel>
Based on my discussion with codechurn, here is what worked for me:
The problem is that ServiceSecurityContext.Current is correctly set until I enter the thread, and then it is NULL. He urged me not to pass it as an argument for the thread, even though it works.
While searching other answers, I found something that works. Thread.CurrentPrincipal.Identity is set to the current identity, which is either a WindowsIdentity or another generic identity. The solution below works even if you do not use windows authentication, in which case there is no need to impersonate.
new Thread(Run).Start(...);
void Run(object args)
{
WindowsImpersonationContext impersonationContext = null;
if (Thread.CurrentPrincipal.Identity is WindowsIdentity)
{
impersonationContext = (Thread.CurrentPrincipal.Identity as WindowsIdentity).Impersonate();
}
try
{
// access the DB as normal
}
catch { /* always handle exceptions in threads */ }
finally
{
if (impersonationContext != null)
{
impersonationContext.Undo();
}
}
}
I'm tearing my hair out on this one, I have a WCF service that I can call through the browser and it works fine, when I call it from the web application with the below method I get a (401) Unauthorized error. And the service does not get called. What's more, when I run my web application from my local machine (debug mode using IIS Express) pointed at my dev server (IIS7) it works but when I deploy my web application to the dev server and point it to the dev server services it fails wit the 401 error. I think this is something to do with IIS7 but I'm not 100% sure and help would be super useful.
I have looked online for the answers but thus far the best I have found is this.
My service call is as follows:
var request = (HttpWebRequest) WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json; charset=utf-8";
request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
request.Credentials = CredentialCache.DefaultCredentials;
WebResponse responce = request.GetResponse();
Stream reader = responce.GetResponseStream();
var sReader = new StreamReader(reader);
string outResult = sReader.ReadToEnd();
sReader.Close();
var result = (T) JsonConvert.DeserializeObject(outResult, typeof (T));
return result;
My configuration for the service looks like this :
<service name="RGMPServices.Householding.Services.AccountService" behaviorConfiguration="Default">
<endpoint address="" kind="webHttpEndpoint" endpointConfiguration="SecuredHttpEndpointBinding" contract="RGMPServices.Householding.Contracts.IAccountService" />
</service>
<service name="RGMPServices.Householding.Services.HouseholdService" behaviorConfiguration="Default">
<endpoint address="" kind="webHttpEndpoint" endpointConfiguration="SecuredHttpEndpointBinding" contract="RGMPServices.Householding.Contracts.IHouseholdService" />
</service>
<service name="RGMPServices.Householding.Services.UserService" behaviorConfiguration="Default">
<endpoint address="" kind="webHttpEndpoint" endpointConfiguration="SecuredHttpEndpointBinding" contract="RGMPServices.Householding.Contracts.IUserService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webBehaviour">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Default">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="SecuredHttpEndpointBinding" helpEnabled="true" automaticFormatSelectionEnabled="true">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
I have put some logging on the client service call, just before I call the service, the response is:
DEBUG 2013-10-01 13:15:13,569 452ms ServiceGetSingle - Passing Login: MYLANDOMAIN\MYLANUSERNAME
ERROR 2013-10-01 13:15:13,631 514ms ServiceGetSingle - ERROR Calling ServiceGetSingle with user credentials login: MYLANDOMAIN\MYLANUSERNAME
System.Net.WebException: The remote server returned an error: (401) Unauthorized.
at System.Net.HttpWebRequest.GetResponse()
at Householding.Common.ServiceHelper.ServiceGetSingle[T](String url)
The code looks like:
logger.Debug("Passing Login: "
+ System.Security.Principal.WindowsIdentity.GetCurrent().Name)
Even when I set the AppPool for my website to my domain account it is still not authorising me to access the WCF Service, but again: it's working for the browser. So weird!
It seems likely you're a victim of the double-hop issue when using Integrated Windows Authentication (IWA) and Kerberos. The first hop is from your browser to the web application; the second hop is from your web application to the WCF service.
Here are some resources that explain the issue more fully, and may offer a solution:
IIS, Windows Authentication and the Double Hop issue
Using Integrated Windows Authentication (IWA) in a Distributed Application Architecture
Understanding Kerberos Double Hop
You can configure Active Directory to support Kerberos delegation (usually the infrastructure guys don't like this), or you could turn off impersonation and use a "service" account for the web application and IIS app pool that can authenticate with the WCF service on behalf of the end user.
What are the default credentials on the Dev server? Try doing a log right there and see what you get.
This is what I suspect: Running locally, the credentials are YOUR windows creds. When you call the dev server from dev, the credentials would be whatever account the website is running under. If that particular account doesn't have access, then it would blow up.
How they said before, this looks like an impersonation problem.
Have you tried to start the client program with "run as" to change the credentials?
Additionally you can change this line of code
request.Credentials = CredentialCache.DefaultCredentials;
to
request.Credentials = new NetworkCredential("MyUsername", "MyPassword");
And see if it works. Also you need to create the account "MyUserName" with "MyPassword" on the web server to make it work.
These errors can be caused when the authenticated user does not have access to the physical path where the WCF service is hosted. On the dev server, open up IIS Manager and navigate to the virtual directory for the service. On the right in the Actions bar, click on "Basic Settings". Below the "Physical Path" text box, click "Connect as...". Choose "Specific User" and try setting it to a user account that you know has rights to the physical folder on the dev server. Typically, this would be a service account whose password does not expire.
When running from the browser, the browser is sending your auth credentials. Also, iis express will run as the logged in user, so this is also sending your credentials. Iis is different, it will be running as a local account. Even if you have authentication on your front end iis, that will not be passed to the backend. Windows impersonation tokens are limited in the number of hops allowed, usually 0. This is done to prevent exactly what you are doing.
If you want front end authentication to flow to the backend then you should probably do the authentication yourself and grab user/pass on the way through. Alternatively, if you do the authentication yourself you can create an impersonation token that allows a hop to another machine and it should work.
I have a Silverlight 5 app that gets some data from a couple Sharepoint lists. It was all working correctly, then we set up the site to allow SSL and I tried to update the service reference to call the webservice using https. It updated the client config binding to use security mode Transport. But when it calls the service it's giving an error:
System.ServiceModel.CommunicationException: An error occurred while trying to make a request to URI 'https://devlpadmin.thelittlegym.com/_vti_bin/Lists.asmx'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details. ---> System.Security.SecurityException ---> System.Security.SecurityException: Security error.
Does anyone know what the problem is or how to get more info than "Security error."?
I've gone through so many different combinations of things that I'm not sure exactly what has happened when, but it's now working. I think originally the site/service was having some weird problem that prompted me to try to manually configure Silverlight to pass NTLM transport credentials. In doing so, I might have created an invalid config file causing the error. The configuration that is working is:
<bindings>
<basicHttpBinding>
<binding name="ListsSoap" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://devadmin.mydomain.com/_vti_bin/Lists.asmx"
binding="basicHttpBinding" bindingConfiguration="ListsSoap"
contract="SPListsService.ListsSoap" name="ListsSoap" />
</client>
So if you're having this error and not making a cross-domain call, suspect some kind underlying service error. If you're not using Silverlight, you can enable tracing to track down the error. If you are using Silverlight, I still don't know what can be done to narrow it down, but be aware that Silverlight only supports a fragment of the configuration options that a normal .net WCF client does.