Tuesday, February 14, 2012

One reason for “The caller was not authenticated by the service” error when WCF proxy is configured via code

Some time ago I faced with the problem when tried to call WCF service from Sharepoint timer job: it threw the following exception: “The caller was not authenticated by the service”. When you will investigate this problem, most of solutions which you will find will say that this error is caused by wsHttpBinding with Message level security, and if you don’t need that – you may rather set security mode to “None” or use basicHttpBinding instead. It was not the case for us – because we had service developed by 3rd party company which used wsHttpBinding and we couldn’t change it.

Notice that we called WCF service from Sharepoint timer job. Sharepoint jobs are executed in separate service process (owstimer). Also we needed to call it from Sharepoint site which is running in w3wp process and has own web.config. When service is called from site we can use web.config for configuring WCF client. But how to configure it when service is called from timer job? Of course you can create owstimer.exe.config file and copy WCF configuration there – but in this case configuration will be duplicated in web.config and in owstimer.exe.config. I found the article which shows interesting solution: Calling WCF Web Services from a SharePoint Timer Job. The idea is that we store WCF configuration in one place (in sites’s web.config. Of course you will need to duplicate it if you have several authentication zones, but if you use SPWebConfigModifications for web.config updates it will be done automatically). In timer job we read web.config using classes from System.Configuration and System.ServiceModel assemblies and construct Binding and EndpointAddress instances using read configuration which then passed to the proxy constructor:

   1: var binding = (Binding)bindingCollectionElement
   2:     .BindingType.GetConstructor(new Type[0]).Invoke(new object[0]);
   3: bindingConfig.ApplyConfiguration(binding);
   4: var address = new EndpointAddress(endpointElement.Address);
   5: var proxy = new MyProxy(binding, address);

I tried to do it like this and got mentioned security exception. As I said all solutions which I found were not applicable for us. After some investigation I found difference between configuration from config and configuration from code: EndpointAddress.Identity property was set to null when it is configured from code.

So I changed code a bit and created EndpointAddress with identity:

   1: private EndpointAddress getEndpointAddress(ChannelEndpointElement endpointElement)
   2: {
   3:     if (endpointElement == null)
   4:     {
   5:         return null;
   6:     }
   8:     if (endpointElement.Identity != null && endpointElement.Identity.Dns != null)
   9:     {
  10:         return new EndpointAddress(endpointElement.Address,
  11:             EndpointIdentity.CreateDnsIdentity(endpointElement.Identity.Dns.Value));
  12:     }
  13:     return new EndpointAddress(endpointElement.Address);
  14: }

In this example I used DNS identity, but you may use your identity type here (e.g. user principal identity). After that error disappeared and service was called successfully.

No comments:

Post a Comment