Thursday, 5 July 2012

WCF - System.Net.WebException: The remote server returned an error: (401) Unauthorized

Further to my previous post about SPNs and WCF applications, there is a further twist in the tale.

As I reminder, I discussed beforehand how I had a self-hosted WCF application that use Windows Security as the Authorisation mechanism. When I deployed this to a remote server, clients could not authenticate correctly unless they had the SPN set in the client code:

var channelFactory = new ChannelFactory<IVolumeService>(
                binding2,
                new EndpointAddress(new Uri(url), new SpnEndpointIdentity("MYSERVICE/MyMachine")
                    ));

Without this setting they would return a 401 Unauthorised, with the detail:
The HTTP request is unauthorized with client authentication scheme 'Negotiate'.



The authentication header received from the server was 'Negotiate oW8wbaADCgEBom
YEZGBiBgkqhkiG9xIBAgIDAH5TMFGgAwIBBaEDAgEepBEYDzIwMTIwNjI5MTY1NjQ1WqUFAgMOYYqmAw
IBKakRGw9HQVpQUk9NVUsuSU5UUkGqEzARoAMCAQGhCjAIGwZhcG90dHM='.
The remote server returned an error: (401) Unauthorized.
The target principal name is incorrect

Adding the SpnEndpointIdentity allowed the clients to work with this remote server.
However, the twist was this: when I went back to working locally, with a local client and a local server running under my current user (with admin privileges) account - it no longer worked!
This time I would get
System.Net.WebException: The remote server returned an error: (401) Unauthorized



.
   at System.Net.HttpWebRequest.GetResponse()
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpCha
nnelRequest.WaitForReply(TimeSpan timeout)

Note there was no The target principal name is incorrect message. It took me a while to figure it, but in the end REMOVING
new SpnEndpointIdentity("MYSERVICE/MyMachine")
got it to work locally! Bah! More reading on SPNs is required.

....

Double Bah! I figured it. It was because I had left a bit of code on the server




var endpointAddress = new EndpointAddress(
                new Uri(ConfigurationManager.AppSettings["WebServiceUrl"]),
                EndpointIdentity.CreateSpnIdentity("HOST/servername.domainname:9011"));

and clearly the SpnIdentity in this mismatched the client. Setting BOTH to be the same caused it to work again.

No, this still didn't work. The solution was to use a UPN rather than a SPN, because the account was running under a user account and not localnetwork or system. Both server and client used:

 var endpointAddress = new EndpointAddress(
                new Uri(ConfigurationManager.AppSettings["WebServiceUrl"]),
                EndpointIdentity.CreateUpnIdentity(WindowsIdentity.GetCurrent().Name));

A hint was also shown by looking at the WSDL of an existing endpoint that ran under a user account:

  <Identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
  <Upn>MYUSER@mydomain.intra</Upn>
  </Identity>
Lessons learnt:
  1. Self hosting WCF services is more painful than hosting them in IIS.
  2. Try and use the configuration approach as much as possible - there are more examples out there.
  3. If you don't publish a metadata exchange you have to bind manually, and this provides more chances to mismatch.
  4. Programmatically defining and consuming the endpoints is less well documented - and self hosting with Windows Authentication and SPNs is sparsely documented!
  5. Generating your client from the metadata exposed from the server is much safer. 

References:
http://msdn.microsoft.com/en-us/library/ms733130.aspx
http://msdn.microsoft.com/en-us/library/ms677601%28v=vs.85%29.aspx
http://social.msdn.microsoft.com/forums/en-US/wcf/thread/78638457-ca7a-4f88-b8a9-9bc32d4b5c7d/
http://msdn.microsoft.com/en-us/library/bb628618.aspx

No comments:

Post a Comment