Monday, May 20, 2013

App.Config in MSTest

From my experience the easiest thing to do is programmatically load an app.config from a known location. 

Add an app.config for your test project, and populate it as required, then add the following code to the ClassInitialize method on any test class that requires the app.config.

            var map = new ExeConfigurationFileMap();
            map.ExeConfigFilename = @"C:\Development\Tests\WcfUtilities\ClientApplication\App.config";
            Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

Standard WCF Proxy Usage Pattern

Using a channel factory gives less grief when you can share a contract assembly between service and client. It is also easier from a unit test point of view (abstract the construction of the channel factory into a virtual method).  Using code generated proxy code is fragile as the generated code is often out of date which may not always fail in an obvious way. Its also easy to end up with duplicate classes in different namespaces on the client when only one exists in the service.

            // Cache ChannelFactory if possible until no longer required.
            // repeatedly creating it can be expensive.
            // TODO - Don't forget to dispose this factory when you're finished with it.
            private ChannelFactory factory =  
                 new ChannelFactory<IService1>("WsHttpBinding_IService1");

            ...

            ICommunicationObject commsObject = null;
            bool success;            
            try
            {
                    var proxy = factory.CreateChannel();                   
                    commsObject = (ICommunicationObject) proxy;
                    var request = new GetDataRequest();
                    var result = proxy.GetDataUsingDataContract(request);
                                // Message pattern - a single request type and a
                                // response type specific for the operation. This
                                // allows independent evolution of operation types.
                    commsObject.Close();
                    success = true;
                    return result;
            }            catch (TimeoutException ex)
            {
                   // TODO - what to do if timeout occurs?
            }
            catch (FaultException ex)
            {
                   // TODO - optional - if the service has its own Fault Exceptions defined.
            }
            catch (CommunicationException ex)
            {
                   // TODO - Any other WCF related exceptions.
            }
            finally
            {
                  if (!success) proxy.Abort();
             }

References:

Stack Overflow - Channel Factory
Microsoft MSDN Article
Stack Overflow - Generated Client Proxy

Wednesday, May 15, 2013

WCF Security: wsHttpBinding Basic Security hosting in IIS


When hosting a service in IIS using Transport security and Basic credentials are required, it is IIS that resolves the basic credentials not the service.  But the service config is still set to SecurityMode Transport with client credential type Basic. Basic auth must be configured and required by IIS config. See here.  If anonymous access is still allowed then the credentials will be ignored. (At least I couldn't make it work when anonymous was allowed as well). When self hosting this scenario, it must be the service that resolves the credentials.  This post only applies to IIS.

Service config is set to this:


            <wsHttpBinding>
                <binding name="HttpBindingConfig">
                    <security mode="Transport">
                        <transport clientCredentialType="Basic"/>
                    </security>
                </binding>
            </wsHttpBinding>

If you omit or set the clientCredentialType to None, you will get this exception.

System.ServiceModel.ServiceActivationException

Basic Auth also needs to be configured explicitly in IIS virtual directory:




The SOAP message that is sent to the service from the client does not contain the credentials, this is distinctly different to using Username credentials in the previous post.


<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
            xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/DoSomething</a:Action>
        <a:MessageID>urn:uuid:f512a5ed-9a56-4460-8c59-f7ed48d3d42c</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <a:To s:mustUnderstand="1">https://localhost/WcfBasicOverWsHttpWithTransport/Service1.svc</a:To>
    </s:Header>
    <s:Body>
        <DoSomething xmlns="http://tempuri.org/">
            <param>Foo bar</param>
        </DoSomething>
    </s:Body>
</s:Envelope>

The credentials in this case are in the HTTP headers. Using Fiddler you can see the credentials being passed.


These credentials are only encoded using Base64 they are NOT encrypted and can easily be reversed back to their username and password text values.This is why SSL is crucial with Basic credentials.

Again same as the last post using Username security, the next likely problem is an exception about the SSL certificate being used in IIS.  Usually in development self-signed certificates are used (who has the money to buy development SSL certs?)
You'll get this error if you are using a self-signed cert:

SecurityNegotiationException

To fix it you'll need to add an event handler to change the default service certificate validation on the client.  THIS IS DEFINITELY NOT RECOMMENDED IN PRODUCTION CODE!  It basically tells the client to ignore all certificate problems.  But when using self-signed certs in dev, it is useful.

 // Ignore any certificate errors - results from using self-signed certs.
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true;

Also the same as the last post you will receive a strange exception when authentication fails:
MessageSecurityException
This is the expected response you'll get when the username or password is incorrect. This is very misleading, because if you search for this, you'll get a great deal of solutions talking about differences in service and client clocks.

Service Contract:


namespace WcfBasicOverWsHttpWithTransport
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        bool DoSomething(string param);
    }
}

Service Implementation

<%@ ServiceHost Language="C#" Debug="true" Service="WcfBasicOverWsHttpWithTransport.Service1" CodeBehind="Service1.svc.cs" %>

namespace WcfBasicOverWsHttpWithTransport
{
    public class Service1 : IService1
    {
        public bool DoSomething(string param)
        {
            return param == "Foo bar";
        }
    }
}

Service web.config:


<?xml version="1.0"?>
<configuration>
    <system.web>
        <compilation debug="true"
                     targetFramework="4.0"/>
        <httpRuntime/>
    </system.web>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="HttpBindingConfig">
                    <security mode="Transport">
                        <transport clientCredentialType="Basic"/>
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <services>
            <service name="WcfBasicOverWsHttpWithTransport.Service1">
                <endpoint address=""
                          binding="wsHttpBinding"
                          contract="WcfBasicOverWsHttpWithTransport.IService1"
                          bindingConfiguration="HttpBindingConfig"/>
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <serviceMetadata httpGetEnabled="true"
                                     httpsGetEnabled="true"/>
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
                                   multipleSiteBindingsEnabled="true"/>
    </system.serviceModel>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <directoryBrowse enabled="true"/>
    </system.webServer>
</configuration>


Client App.config

<?xml version="1.0"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IService1">
                    <security mode="Transport">
                        <transport clientCredentialType="None" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://localhost/WcfBasicOverWsHttpWithTransport/Service1.svc"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
                contract="ServiceReference1.IService1" name="WSHttpBinding_IService1" />
        </client>
    </system.serviceModel>
</configuration>

Client code

namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread.Sleep(1000);

            // Ignore any certificate errors - results from using self-signed certs.
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true;

            var ep = new EndpointAddress(new Uri("https://localhost/WcfBasicOverWsHttpWithTransport/Service1.svc"));
            var binding = new WSHttpBinding(SecurityMode.Transport)
                                  {
                                      SendTimeout = TimeSpan.FromSeconds(70),
                                      BypassProxyOnLocal = false
                                  };
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
            var proxy = new ServiceReference1.Service1Client(binding, ep);
            proxy.ClientCredentials.UserName.UserName = "username1";
            proxy.ClientCredentials.UserName.Password = "pass99"; 

            object response = string.Empty;
            try
            {
                response = proxy.DoSomething("Foo bar");
                Console.WriteLine("Authenticated.");
            }
            catch (MessageSecurityException)
            {
                Console.WriteLine("Your password is wrong.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Something else went wrong \n" + ex);
            }

            Console.WriteLine(response.ToString());
        }
    }
}


WCF Security: wsHttpBinding with a Custom User Name Validator (IIS)

This post covers hosting a WCF service in IIS using wsHttpBinding, a custom Username Password validator and using TransportWithMessageCredential security mode.  Also keep in mind that some security modes behave differently under IIS than self hosting. This post only applies to hosting in IIS. See this post for using Basic Security with IIS.


To use custom user name/password style credentials with an IIS hosted Http service the following config is used:


            <wsHttpBinding>

                <binding name="UserNameHttpBindingConfig">
                    <security mode="TransportWithMessageCredential">
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </wsHttpBinding>

NOTE: The way the credentials are passed to the service are distinctly different to Http Basic credentials - this is not Basic Auth.  What is actually happening is the credentials are passed as part of the SOAP envelope.

<?xml version="1.0" encoding="utf-8" ?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
            xmlns:a="http://www.w3.org/2005/08/addressing"
            xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT</a:Action>
        <a:MessageID>urn:uuid:1a54b138-c3aa-46a5-b80c-1581aa6432b8</a:MessageID>
        <a:ReplyTo>
            <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
        </a:ReplyTo>
        <a:To s:mustUnderstand="1">https://localhost/WcfUsernameOverWsHttpWithTransport/Service1.svc</a:To>
        <o:Security s:mustUnderstand="1"
                    xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <u:Timestamp u:Id="_0">
                <u:Created>2013-05-14T21:17:03.746Z</u:Created>
                <u:Expires>2013-05-14T21:22:03.746Z</u:Expires>
            </u:Timestamp>
            <o:UsernameToken u:Id="uuid-8f7509e1-b84b-4291-adb5-23ae3f83d2cd-1">
                <o:Username>
                    <!-- Removed-->
                </o:Username>
                <o:Password>
                    <!-- Removed-->
                </o:Password>
            </o:UsernameToken>
        </o:Security>
    </s:Header>
    <s:Body>
        <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
            <t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
            <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
            <t:Entropy>
                <!-- Removed-->
            </t:Entropy>
            <t:KeySize>256</t:KeySize>
        </t:RequestSecurityToken>
    </s:Body>
</s:Envelope>



The next likely problem is an exception about the SSL certificate being used in IIS.  Usually in development self-signed certificates are used (who has the money to buy development SSL certs?)
You'll get this error if you are using a self-signed cert:

SecurityNegotiationException

To fix it you'll need to add an event handler to change the default service certificate validation on the client.  THIS IS DEFINITELY NOT RECOMMENDED IN PRODUCTION CODE!  It basically tells the client to ignore all certificate problems.  But when using self-signed certs in dev, it is useful.

 // Ignore any certificate errors - results from using self-signed certs.
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true;




You will get a strange exception when authentication fails:
MessageSecurityException
This is the expected response you'll get when the password is incorrect and any Exception is thrown from the custom validator (including Fault Exceptions). This is very misleading, because if you search for this, you'll get a great deal of solutions talking about differences in service and client clocks.

Service Contract:


namespace WcfUsernameOverWsHttpWithTransport
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        bool DoSomething(string param);
    }
}

Service Implementation

<%@ ServiceHost Language="C#" Debug="true" Service="WcfUsernameOverWsHttpWithTransport.Service1" CodeBehind="Service1.svc.cs" %>

namespace WcfUsernameOverWsHttpWithTransport
{
    public class Service1 : IService1
    {
        public bool DoSomething(string param)
        {
            return param == "Foo bar";
        }
    }
}

Service web.config:

Note: targeting .Net 4.0 or 4.5 does not impact this example, either works fine.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
    <httpRuntime/>
  </system.web>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="HttpBindingConfig">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="WcfUsernameOverWsHttpWithTransport.Service1">
        <endpoint address="" binding="wsHttpBinding" contract="WcfUsernameOverWsHttpWithTransport.IService1" bindingConfiguration="HttpBindingConfig"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfUsernameOverWsHttpWithTransport.UserNameValidator,WcfUsernameOverWsHttpWithTransport"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>

Client App.Config




<?xml version="1.0"?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IService1">
                    <security mode="TransportWithMessageCredential">
                        <transport clientCredentialType="None" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://localhost/WcfUsernameOverWsHttpWithTransport/Service1.svc"
                binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1"
                contract="ServiceReference1.IService1" name="WSHttpBinding_IService1" />
        </client>
    </system.serviceModel>
</configuration>


Client Proxy Code:


namespace ClientConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread.Sleep(1000);

            // Ignore any certificate errors - results from using self-signed certs.
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => true;

            var ep = new EndpointAddress(new Uri("https://localhost/WcfUsernameOverWsHttpWithTransport/Service1.svc"));
            var binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential)
                                  {
                                      SendTimeout = TimeSpan.FromSeconds(70),
                                      BypassProxyOnLocal = false
                                  };
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            var proxy = new ServiceReference1.Service1Client(binding, ep);
            proxy.ClientCredentials.UserName.UserName = "test";
            proxy.ClientCredentials.UserName.Password = "pass99"; // pass99

            object response = string.Empty;
            try
            {
                response = proxy.DoSomething("Foo bar");
                Console.WriteLine("Authenticated.");
            }
            catch (MessageSecurityException)
            {
                Console.WriteLine("Your password is wrong.");
            }

            Console.WriteLine(response.ToString());
        }
    }
}



References


Monday, May 13, 2013

Configuring Impersonation in WCF

First of all, there must be a very strong reason to use impersonation.  General consensus these days, is to avoid it.  This is generally because the further down the stack you travel away from the UI the more meaningless the user's credentials become.  Prefer instead to use a "Trusted Subsystem" approach where once the user is authenticated and authorised the subsystems downstream "trust" the upstream systems.

Valid reasons could be integrating with legacy systems or infrastructure requirements.

On the client:

  • Use a binding and security mode that supports passing credentials.
  • The client app must agree to allow the service impersonating its credentials.


            <endpointBehaviors>
                <behavior name="MyEndpointBehaviour">
                    <clientCredentials>
                        <windows allowedImpersonationLevel="Impersonation"/>
                    </clientCredentials>
                </behavior>
            </endpointBehaviors>

On the service:


  • Decorate the service contract implementation with a operation behaviour that allows impersonation.
     [OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
    Possible values are NotAllowed, Allowed or Required. In this case I used Allowed, because Impersonation is one option that could be used when deployed.  (The other option is running the App-Pool with credentials that can access downstream systems, for example SQL).
    
    
  • The service app.config must enable impersonation also.
                <serviceBehaviors>
                    <behavior>
                        <serviceAuthorization impersonateCallerForAllOperations="true" />
                    </behavior>
                </serviceBehaviors>
I found it useful when impersonation is required, to use the Allowed option on the service implementation.  This means that the remainder of the work is config, so you can change it after its deployed.

If you get any weird exceptions while testing, then the most common issue is not configuring the client to allow the service to impersonate it.  This is done with the endpointBehaviour snippet above.  In my case I was getting a service exception in the type initializer inside System.Data.SqlClient.SqlConnection and the inner exception is "requested registry access is not allowed"; enabling impersonation from the client side of the call fixed it.