Category Archives: WCF

I wish WCF can provide something like this:

instead of

[PrincipalPermission(SecurityAction.Demand, Role ="Administrators")]

We can use

[PrincipalPermission(SecurityAction.Demand, RoleProvider = typeof(MyAuthorizationChecker)]

So developers won’t care if security requirement changed.

I posted before about explicitly calling

permissionSet.Demand()

right in service method, it works, but it would be nice to use service behavior attribute to make code looks cleaner.

Extending WCF service is not that hard, thanks to this post.

    public class CheckAuthorizationBehaviorAttribute : Attribute, IOperationBehavior
    {
        public string MethodName { get; set; }

        public void Validate(OperationDescription operationDescription)
        {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new AuthorizationChecker(dispatchOperation.Invoker
                , MethodName);
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {

        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {

        }
    }

    public class AuthorizationChecker : IOperationInvoker
    {
        private readonly IOperationInvoker _invoker;
        private readonly string _methodName;

        public AuthorizationChecker(IOperationInvoker invoker, string methodName)
        {
            _invoker = invoker;
            _methodName = methodName;
        }

        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
             Console.WriteLine("Checking permission on methodname {0}.{1}", instance.ToString(),_methodName);

            if (!.....(my authorization logic here ))
                throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException("No access"))
                ;

            string value;
            value = (string)_invoker.Invoke(instance, inputs, out outputs);
            return value;

        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return _invoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return _invoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return _invoker.IsSynchronous; }
        }
    }

Then my service contract is just like this:

        [ServiceContract]
        public interface ITestService
        {
            [CheckAuthorizationBehavior(MethodName = "PingWithUserName")]
            [OperationContract]
            void Ping(string username);

I was having difficult to get the method name inside invoker, the methodname is hide in the private syncInvoker class, so I had to add the public attribute to my behavior. Later I found out this might be a better idea, because what if the method is an overload one?

Still, it would be nice to set the default method name to the one we just attached. Hope I can figure this out soon.

This should be a simple configuration trick, just do it, increasing client side app.config.

            var binding = new WSHttpBinding();
            binding.MaxReceivedMessageSize = 2147483647;
            XmlDictionaryReaderQuotas readerQuotas = XmlDictionaryReaderQuotas.Max;

            binding.ReaderQuotas = readerQuotas;
            channelFactory = new ChannelFactory<IRequestManagementService>(
                binding,
                new EndpointAddress(_url));

             var _proxy = channelFactory.CreateChannel();

But this isn’t enough, you need to change the same setting on serverside as well. Unfortunately, the default web.config auto-generated by VS doesn’t have binding section at all, so you have to add this by your own. Here is an example:

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="..."
        name="..."
               >
        <endpoint address="" binding="wsHttpBinding"
                  bindingConfiguration="maxStringBinding"
                  contract="I...Service">

          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="soap" binding="basicHttpBinding" contract="I...Service" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>...        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <wsHttpBinding>
        <binding
         name="maxStringBinding"
         maxReceivedMessageSize="2147483647">
          <readerQuotas
            maxDepth="2147483647"
            maxStringContentLength="2147483647"
            maxArrayLength="2147483647"
            maxBytesPerRead="2147483647"
            maxNameTableCharCount="2147483647" />
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>

Using Exception in Wcf should be very caucious, as I blogged before, wcf exception should be marked as Serializable, and implement all 4 ctor from base exception class. An unit-test for each exception might be overkill, but it’s worth to try.

In-proc wcf host is neat for this purpose, by using channel Factory, client side doesn’t need configuration either.

    [TestFixture]
    public class MyDataNotFoundExceptionSpecs
    {
        private readonly ServiceHost _host;
        private const string _url = "net.tcp://localhost:9000/TestException";

        public MyDataNotFoundExceptionSpecs()
        {
            _host = new ServiceHost(typeof(TestService));

            _host.AddServiceEndpoint(typeof(ITestService),
                new NetTcpBinding(), _url);
            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        ~MyDataNotFoundExceptionSpecs()
        {
            _host.Close();
        }
            private ChannelFactory<ITestService> channelFactory;
            [SetUp]
            public void SetupChannelFacotry()
            {
                channelFactory = new ChannelFactory<ITestService>(
                   new NetTcpBinding(),
                   new EndpointAddress(_url));
            }
            [TearDown]
            public void CleanUpChannelFacotry()
            {
                    channelFactory.Abort();
            }
        [Test]
        [ExpectedException(typeof(FaultException<MyDataNotFoundException>))]
        public void should_passed_to_client()
        {
            ITestService proxy = channelFactory.CreateChannel();

            proxy.Ping("Frank");

          }

    }

    public class TestService : ITestService
    {
        public void Ping(string user)
        {
            throw new FaultException<MyDataNotFoundException>(new MyDataNotFoundException());
        }
    }

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        [FaultContract(typeof(MyDataNotFoundException))]
        void Ping(string user);
    }

For unit-test purpose, it’s necessary to make WCF client/proxy class implement from an interface.

The problem is, we suppose to clean up this WCF client after each service call, which means it should be able to repeatedly contracted and disposed. From IOC container we can only get it once. A factory for WCF client is the only solution.

There is an OSS lib from SoftwareIsHardwork. Very detail document.

I personally like Michael Perry’s POCO better:  Client object is created and closed in each service method call, and method is wrapped in lambda expression, very clean and smart.

In my previous post, I was using PrincipalPermissionAttribute, the limitation of this solution is, developers have to hardcode the authorized role list into code. To improve it, we can call Demand() method explicitly.


// WCF Service
public void DeleteEmailRole(EmailRoleDto emailRoleDto)
 {
// Will throw SecurityException and then WCF will convert it to SecurityAccessDeniedException
// to WCF client side, so we don't need add SecurityException to Contract, because it will not be
    // caught as FaultException.
    AuthorizationChecker.CheckPermissionForCurrentUserOn("DeleteEmailRole");
EmailService.Delete(emailRoleDto);
 }

public class AuthorizationChecker : IAuthorizationChecker
 {
private readonly IAuthRoleRepository _authRoleRepository;

public AuthorizationChecker(IAuthRoleRepository authRoleRepository)
{
_authRoleRepository = authRoleRepository;
}

/// <summary>
/// Can be used by UI to enable/disable buttons/menus.
/// </summary>
public bool IsCurrentUserAllowedTo(string serviceName)
{
try
{
CheckPermissionForCurrentUserOn(serviceName);
}
catch (SecurityException)
{
return false;
}

return true;
}

public void CheckPermissionForCurrentUserOn(string serviceName)
{
IPermission permissionSet = null;

foreach (var role in
_authRoleRepository.FetchAllRolesHavingAccessTo(serviceName))
{
var permission = new PrincipalPermission(null, role);
permissionSet = permissionSet == null ?  permission : permissionSet.Union(permission);
}

// Demand will throw Security exception if user has no permission defined in authRoleRepository.
if (permissionSet != null) permissionSet.Demand();
}
 }

// Client code
protected void HandleException(Exception e)
{

if (e is SecurityAccessDeniedException)
{
_messageBox.Show("Sorry you don't have permission to this method.\n" + e.Message);
throw e;
}

}

I was struggling the similar problem as this one, , my customized exception (inherited from exception class) could not be sent to WCF client, once I changed it to regular exception (?) class, it works  as expected. What’s wrong here?

It turns out it was inheritance problem, I missed 2 things: according MSDN guidline (http://msdn.microsoft.com/en-us/library/ms229064(VS.80).aspx)

  1. My customized exception should implement the common ctor accepting (SerializationInfo info, StreamingContext context)
  2. Customized field in my exception needs special storage process done by GetObjectData, and parsing in the common ctor mentioned in 1.

My exception class:


[Serializable]
 public class MyValidationException : Exception
 {
    public IEnumerable<string> ValidationErrors { get; private set; }

    public MyValidationException(IEnumerable<string> validationErrors)
    {
        ValidationErrors = validationErrors;
    }

    protected MyValidationException(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        ValidationErrors = info.GetString("ValidationErrors").Split(new char[]{'\n'});
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("ValidationErrors", string.Join("\n",ValidationErrors.ToArray()));
    }

Option B might be to create completely seperate Fault class to re-wrap this exception when doing Converting exception to fault, in which we can take the customized field as ctor parameter to my Fault class.


[Serializable]
public class MyValidationFault : MyBaseFault
{
   public IEnumerable<string> Errors { get; private set; }

   public MyValidationFault(IEnumerable<string> errors, string message)
    : base(message)
   {
     Errors = errors;
   }
}

public static FaultException ConvertExceptionToFault(Exception ex)
{
   if (ex is MyValidationException)
   {
     var vex = (MyValidationException)ex;
     return new FaultException<MyValidationError>(
      new MyValidationError(vex.ValidationErrors, ex.Message)
        , "Validation Failed"
      );
   }

    ...

This disadvantage of creating customized fault solution is, if WCF client use service reference to create client proxy, each proxy will save local proxy types into its own namespace, those common fault classes will be duplicate. It might be a good idea to move those fault into a shared project between server and client.

GenericADOException

Many people start using UoW pattern in WCF by different ways, both Andreas and IgooCoder are using WCF extension. IgooCoder Common removes extension in IDispatchMessageInspector.BeforeSendReply() and then commit  UoW in IExtension.Detach(),  while Andeas commits right in BeforeSendReply.

If exception happens in commit, it won’t be caught by provideFault(), because it’s out of method scope. FaultContract defined at method header doesn’t effect thos commit related exception. They will reach down to UI as general FaultException.

To catch those kind of framework level exception at client side,


protected void HandleException(Exception e){
     if (e is FaultException)
     {
            if( e is FaultException<MyValidationFault>)
            { ...
            }
            else
            {
              _messageBox.Show(((FaultException<ExceptionDetail>)e).Detail.InnerException.Message);
              throw e;
            }
            return;
      }

This means we should only define customized exception in FaultContract.

Wcf Test Client tool shipped with SDK (C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\WcfTestClient.exe) is very useful, looks like the WCF request and response is soap-like message passed around. But I have to publish to a real soap web service to enable other language consume Wcf Service, adding an extra endpoint using basicHttpBinding:

<endpoint address=”soap” binding=”basicHttpBinding” contract=”WcfServices.Contracts.ILookupServiceContract” />

My PowerBuilder client can consume this WCF/Soap webservice now:

  1. Add pbwsclientXXX.pbd to lib list.
  2. Use Web Service proxy wizard, generate local proxy for powerbuilder. (My VMWare environment caused lots of trouble, watch out your firewall/router/http proxy) By adding host into ignore list of IE6, I fixed the wsdl not access problem, but still can’t fix out the same problem on VMWare and IE7.
  3. Code like this:
    soapconnection gnv_con
    gnv_con = create soapconnection
    
    wcflookupservice lnv_lookup
    gnv_con.createinstance( lnv_lookup, 'wcflookupservice' )
    any list[]
    string email_roles[]
    list = lnv_lookup.FetchAllEmailRoles( )
    
    email_roles = list
    
    int i
    for i = 1 to UpperBound(email_roles)
    lb_1.additem( email_roles[i] )
    Next
    

Ruby client can use SOAP::WSDLDriverFactory, example can be found at RESTful Web Services, or this post.  I kept getting “400 Bad Request” error, looked into the request envelope, it turns out I need to force Ruby running in Unicode mode, to change the KCode for XSD::Charset, run Ruby in ‘-Ku’ switch.

For Ruby, the return type from web service is always SOAP::Mapping::Object type.

To parse it, look into the output, for my case:

#<SOAP::Mapping::Object:0×423a8b2 {http://tempuri.org/}FetchAllEmailRolesResult=#<SOAP::Mapping::Object:0×423a7ae {http://schemas.microsoft.com/2003/10/Serialization/Arrays}string=["test 1736582024", "test 1734736950",  "Business Alternate", "Contact Alternate", "test 1658442384", "test 1576477246", "test 518224939", "Contact"]>>

The the parse statement is:


require "soap/wsdlDriver"

def test3
wsdl_url = "http://myhost/RequestManagement/RequestService.svc?wsdl"
driver = SOAP::WSDLDriverFactory.new(wsdl_url).create_rpc_driver

result_set = driver.FetchAllEmailRolesResult("")

result_set
end

p test3['FetchAllEmailRolesResult']['string']

Unfortunately, Google already discontinued their SOAP search API, it’s hard to run the google search example from RESTful Web Services? Can somebody send me an valid Google SOAP search api key?

Another issue with this SOAP::WSDLDriverFactory, how to control name space in request? Because we added the name space into our service contract.


[ServiceContract(Namespace = "Stakeholder")]
public interface IOrganizationServiceContract
{
[OperationContract]
OrganizationDto FetchOrganizationById(long id);
}
Ruby (method not found) Wcf Test Client (Test Passed)
ruby/lib/ruby/1.8/wsdl/operationBinding.rb:40:in `find_operation':
{http://tempuri.org/}FetchOrganizationById not found (RuntimeError)

&amp;amp;lt;s:Header&amp;amp;gt;
&amp;amp;lt;a:Action s:mustUnderstand="1"&amp;amp;gt;Stakeholder/IOrganizationServiceContract/FetchOrganizationById&amp;amp;lt;/a:Action&amp;amp;gt;
&amp;amp;lt;a:MessageID&amp;amp;gt;urn:uuid:60e56fd8-7d46-44db-8b79-61c5e4784a85&amp;amp;lt;/a:MessageID&amp;amp;gt;
&amp;amp;lt;a:ReplyTo&amp;amp;gt;
&amp;amp;lt;a:Address&amp;amp;gt;http://www.w3.org/2005/08/addressing/anonymous&amp;amp;lt;/a:Address&amp;amp;gt;
&amp;amp;lt;/a:ReplyTo&amp;amp;gt;
&amp;amp;lt;/s:Header&amp;amp;gt;
&amp;amp;lt;s:Body&amp;amp;gt;
&amp;amp;lt;FetchOrganizationById xmlns="Stakeholder"&amp;amp;gt;
&amp;amp;lt;id&amp;amp;gt;8172&amp;amp;lt;/id&amp;amp;gt;
&amp;amp;lt;/FetchOrganizationById&amp;amp;gt;
&amp;amp;lt;/s:Body&amp;amp;gt;
&amp;amp;lt;/s:Envelope&amp;amp;gt;

RESTful service is the way to go, here is a simple demo I learned from how to setup WCF in RESTful service.

When Wcf service throw an exception, if will go down to client and kill its process if client didn’t handle this type of exception.

We need to wrap/convert some exception to FaultException<exception> to avoid this native unfriendly behavior.

Unless FaultException<SomeTypeOfError> is declared in ServiceContract, the fault will reach to client side in the format of FaultException, with no embedded Detail exception object in it.

This embeded SomeTypeOfError doesn’t need to be an exception type, it only needs to be declared as a serializable class.

I did have problem when using exception as the FaultError Type, the service proxy can’t generate any code but the exception at client side. I haven’t see any benefit of pass Exception as FaultDetail (prompt exception?), so it’s not a big problem for me, yet.

An ErrorHandler extension can be used to at service side, to wrap the specific exception to Fault through attribute syntax at class level, which will make service code much clean.

ErrorHandler extension sample code can be downloaded from IDesign website, it only converts the exception declared in Contract to Fault. There is another example which is converting all exception to Fault, similar as I’m doing in my project. I think any kind of the service exception should not kill client process, no matter it’s dumb or smart in handling exception. I’ve changed convert all exception to convert only some known exceptions to fault, unknown/unhandled  exception should pass to client as it is. In fact WCF will convert them to FaultException automatically.

It would be nice if we can do similar attribute syntax at client proxy side as well. The CallBack stuff from IDesign is overkill for this requirement. Some simple attribute extension shouldn’t be very hard to implement. Using PostSharp OnExceptionAspect is the easiest solution.


        public EmailRoleDto AddNewEmailRole(string roleName)
        {
           try

           {
                return _underlyingConnection.AddNewEmailRole(roleName);
           }
            catch (Exception ex)
            {
                HandleException(ex);
            }
         }
// Note: HandleException() only handle those exceptions need to handle like validation or security
// while other unknown exception should just let them go to kill the app.
// UI should deal with those seperatelly.

Can be simplified to


 [WcfProxyErrorHandler]
 public EmailRoleDto AddNewEmailRole(string roleName)
 {
 return _underlyingConnection.AddNewEmailRole(roleName);
 }

 [Serializable]
 public class WcfProxyErrorHandlerAttribute : OnExceptionAspect
 {
 public override void OnException(MethodExecutionEventArgs eventArgs)
 {
 // default is ReThrowException
 eventArgs.FlowBehavior = FlowBehavior.Return;

 base.OnException(eventArgs);
 var errorHandlerHelper = ObjectFactory.GetInstance();

 errorHandlerHelper.HandleException(eventArgs.Exception);
 }

 }

But pre-build PostSharp is pretty hard to implement in Nant, here are some reasons why we should avoid using PostSharp to handle exceptions.

Exception Application Block from EntLib is another option.

ErrorHandler extension

When using UserName instead of the default Windows authentication, the idea of using Custom username validator can only do the authentication part, while the authorization always needs more info besides the username, usually it’s a list of roles. We could create a CustomAuthorizationPolicy : IAuthorizationPolicy, then fill out the evalute method something like this:


         <behavior name="customAuthPolicy">
            <serviceMetadata httpGetEnabled="true"/>
            <serviceDebug includeExceptionDetailInFaults="true"/>

            <serviceAuthorization principalPermissionMode ="Custom" >
                <authorizationPolicies>
                  <add policyType="Premotion.Services.CustomAuthorizationPolicy, App_Code" />
                </authorizationPolicies>
            </serviceAuthorization>
            <serviceCredentials>
                <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
                <userNameAuthentication userNamePasswordValidationMode="Custom"
                                        customUserNamePasswordValidatorType="Premotion.Services.UsernameValidator, App_Code" />
            </serviceCredentials>
        </behavior>

    public class CustomAuthorizationPolicy : IAuthorizationPolicy
    {
        public string Id
        {
            get { return new Guid().ToString(); }
        }

        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
            // get the authenticated client identity
            IIdentity client = GetClientIdentity(evaluationContext);

            List<string> roles = new List<string>();
            if (client.Name == "frank") roles.Add("Admins");

            evaluationContext.Properties["Principal"] = new CustomPrincipal(client, roles.ToArray());

            return true;
        }

        public ClaimSet Issuer
        {
            get { return ClaimSet.System; }
        }

        private IIdentity GetClientIdentity(EvaluationContext evaluationContext)
        {
            object obj;
            if (!evaluationContext.Properties.TryGetValue("Identities", out obj))
                throw new Exception("No Identity found");

            IList<IIdentity> identities = obj as IList<IIdentity>;
            if (obj == null || identities.Count <= 0)
                throw new Exception("No Identity found");

            return identities[0];
        }
    }

Not only the relection code to get identity from  evalution context,  to create our own CustomPriciple and CustomIdentiy class is also very annoying, even it’s simple, but, can we make it simpler. What we need just a list of roles based on the username.

    /// <summary>
    /// Make sure to generate the test certificate
    /// C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin
    /// makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe
    /// </summary>
    [TestFixture]
    public class UsernameWcfSpecs
    {
        private ServiceHost _host;
        private const string _url = "http://localhost:9000/TestUsernameAuthentication";

        private void SetupWcfHostTakingWindowsLogin()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows; // default

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);
            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostTakingUsernameButValidateItAgainstWindows()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Windows; // default

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostTakingUsernameAndDoCustomValidation()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Custom; // default is windows

            _host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.None; // default is UseWindowGroups
            _host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator =
                new CustomUsernameValidator(new MyActiveDirectoryService());

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private void SetupWcfHostUsingUsernameAuthCustomValidationWithAuthorization()
        {
            _host = new ServiceHost(typeof (TestService));

            var binding = new WSHttpBinding();
            binding.Security.Mode = SecurityMode.Message; // default

            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; // default is windows

            // protect message sent from client and certificate host
            _host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My,
                                                                X509FindType.FindBySubjectName, "MyServerCert");

            _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode =
                UserNamePasswordValidationMode.Custom; // default is windows
            var policies = new List<IAuthorizationPolicy>();
            policies.Add(new CustomAuthorizationPolicy(new MyActiveDirectoryService()));
            _host.Authorization.ExternalAuthorizationPolicies = policies.AsReadOnly();
            _host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom; // default is UseWindowGroups

            _host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator =
                new CustomUsernameValidator(new MyActiveDirectoryService());

            _host.AddServiceEndpoint(typeof (ITestService), binding, _url);

            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        private ChannelFactory<ITestService> _channelFactory;
        private bool _success;

        [TearDown]
        public void CleanUpChannelFacotry()
        {
            if (_success)
            {
                _channelFactory.Close();
            }
            else
            {
                _channelFactory.Abort();
            }

            _host.Close();
            Console.WriteLine("Wcf host stopped.");
        }

        [Test]
        public void should_connect_to_host_taking_windows_auth()
        {
            SetupWcfHostTakingWindowsLogin();

            var binding = new WSHttpBinding();

            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(_url));

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            _success = true;
        }

        [Test]
        public void should_connect_to_host_taking_username_auth_windows_login()
        {
            SetupWcfHostTakingUsernameButValidateItAgainstWindows();

            var binding = new WSHttpBinding();
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
            binding.Security.Mode = SecurityMode.Message;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            _success = true;
        }

        [Test]
        public void should_connect_to_host_takeing_username_validation()
        {
            SetupWcfHostTakingUsernameAndDoCutomValidation();

            var binding = new WSHttpBinding();

            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            bool noAccess = false;
            try
            {
                proxy.CmisUsersOnly();
            }
            catch (SecurityAccessDeniedException)
            {
                noAccess = true;
            }
            Assert.That(noAccess);

            _success = true;
        }

        [Test]
        public void should_connect_to_host_takeing_username_validation_with_authorization()
        {
            SetupWcfHostUsingUsernameAuthCustomValidationWithAuthorization();

            var binding = new WSHttpBinding();

            binding.Security.Mode = SecurityMode.Message;
            binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

            // usually certificate should be same as the domain name of service host, so set identity at client to bypass this check.
            _channelFactory = new ChannelFactory<ITestService>(
                binding,
                new EndpointAddress(new Uri(_url), EndpointIdentity.CreateDnsIdentity("MyServerCert")));

            // Ignore certificate, call could be redirected to a malicious service (through client address resolving)
            _channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;

            _channelFactory.Credentials.UserName.UserName = "guest";
            _channelFactory.Credentials.UserName.Password = "hello";

            ITestService proxy = _channelFactory.CreateChannel();

            proxy.Ping();
            proxy.CmisUsersOnly();
            _success = true;
        }

AspSqlMembership

In fact, MS provide a default Role-based AspSqlMembership Provider for WCF, with a pre-defined database and whole set of membership provider / role manager classes. Fortunately, We can easily override the classes to let WCF to go against our own credential store.


        <behavior name="AglcSqlMembership">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceAuthorization principalPermissionMode ="UseAspNetRoles" />
          <serviceCredentials>
            <serviceCertificate findValue="MyServerCert" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
            <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" />
          </serviceCredentials>
        </behavior>

...

    <system.web>
    <membership defaultProvider ="MySqlMembershipProvider" >
<providers>
        <add name ="MySqlMembershipProvider"
             type ="Premotion.Services.MySqlMembershipProvider"
             connectionStringName ="WcfUsers"
          />
      </providers>
     </membership>

    <roleManager enabled="true" defaultProvider ="MySqlRoleManager">
<providers>
        <add name ="MySqlRoleManager"
             type ="Premotion.Services.MySqlRoleManager"
             connectionStringName ="WcfUsers"
             />
      </providers>
    </roleManager>
  </system.web>
  <connectionStrings>
    <add name="WcfUsers"  connectionString ="data souce=.\SQLEXPRESS; Integrated Security=SSPI; Initial Catalog=WcfUsers" />
  </connectionStrings>

    public class MySqlMembershipProvider : SqlMembershipProvider
    {
        public override bool ValidateUser(string username, string password)
        {
            // check if the user is not test
            if (username == "test1" && password == "test1") return true;
            return false;
        }

    public class MySqlRoleManager : SqlRoleProvider
    {
        public override bool IsUserInRole(string username, string roleName)
        {
            if (username == "test1" && roleName == "Admin") return true;
            return false;
        }
    }  

  }

public class CoreService: ICoreServiceContract
{
        [PrincipalPermission(SecurityAction.Demand, Role="Admin")]
        public void AdminsOnly()
        {

        }
}

public string Hello()
{
   OperationContext context = OperationContext.Current;

   MessageProperties messageProperties = context.IncomingMessageProperties;

   RemoteEndpointMessageProperty endpointProperty =
     messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

   return string.Format("Hello {0}! Your IP address is {1} and your port is {2}",
      ServiceSecurityContext.Current.PrimaryIdentity.Name, endpointProperty.Address, endpointProperty.Port);
   // If it's Windows authorization
   // return "How are you ! " + Thread.CurrentPrincipal.Identity.Name;
 }

I had to add a connectionStringName in those xml definition part, even I’m not using it at all.

Some interesting differences,

security mode UserNamePassword Validation Mode Authorization PrincipalPermissionMode Authentication Type principle type
Windows Kerberos WindowsPrinciple
UserName Windows anyone but Custom NTLM WindowsPrinciple
UserName Custom (must apply CutomAuthorziationPolicy, otherwise the Thread.CurrentPrincipal is not authenticated while ServiceSecurityContext is ) Custom CustomUsernameValidator
UserName MembershipProvider UseAspNetRoles MembershipProviderValidator GenricPrinciple

Updates: The WsHttpBinding using Windows auth which sendng Kerberos auth will fail on Vista client, the error message is “The message or signature supplied for verification has been altered”. Microsoft admit it’s a bug.

The default wsHttpBinding is perfect for intranet application scenario, if all users are login throug a LDAP server. Client side already authenticated the user, on server side, services only need to grap  the client user windows login (with domain).

Thread.CurrentPrincipal.Identity.Name

In UserName auth mode, this becomes a blank string, to get user id inputted in some kind of login page, call:

ServiceSecurityContext.Current.PrimaryIdentity.Name

UserName auth mode should be used for internet scenario, WCF uses the X509 certificate to protect the message transfered over plain HTTP.  Service side need to generate the certificate by running: (in C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin )

makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=MyServerCert -sky exchange –pe

Then configure certificate in web.config:

<behavior name="customUserName">
	<serviceDebug includeExceptionDetailInFaults="true"/>
	<serviceMetadata httpGetEnabled="true"/>
	<serviceCredentials>
		<serviceCertificate
			findValue="MyServerCert"
			x509FindType="FindBySubjectName"
			storeLocation="LocalMachine"
			storeName="My"
		/>
 	</serviceCredentials>
</behavior> 

Got “keyset does not exist” error then, had to give read permission to the account to server the www service, either ASPNET or NETWORK SERVICE. Make sure you get the right key file, I couldn’t make cacls work with folder.

cacls.exe "C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys\your-key-file" /E /G "ASPNET":R
cacls.exe "C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys\your-key-file" /E /G "NETWORK SERVICE":R

Client side has 5 options to choose as the WSHttpbinding behavior:

        <behavior name="PeerTrustClient">
          <clientCredentials>
            <serviceCertificate>
              <authentication certificateValidationMode="PeerTrust"  />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
            <!-- None, ChainTrust, or Custom-->
  1. None.

    Skip the certificate check, client doesn't care if the host is the REAL service provider. Some hackers could resolve chient's address and redirect the call to a malicious service.

  2. PeerTrust

    Certificate (exported private key?) installed in the client\'s Trusted People store. Maybe double click the certificate file can do the job.

  3. ChianTrust

    Certificate issued by a root authority (can get a trial one from Thawte). I haven\'t figure this out yet, \'Windows does not have enough information to verify this certificate\'. Downloaded and installed the Thawte test CA root certificate, same problem.

    Switched to SSL Web Server Certificate, the default was SGC Supercert, which needs a Thawte Test Intermediate CA I couldn't find anywhere. The SSL Web Server certificate at least looks OK, with the Thawte Test Root CA installed,  no error or warning appear in mmc.exe view.

    But my WCF service still reports:  'System.IdentityModel.Tokens.SecurityTokenValidationException: The X.509 certificate CN=... chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. An internal certificate chaining error has occurred'.

    Switched back to SGC test certificate from thawte, re-do the same thing, this time the "not enough information " warning magically went away! And WCF services feedback a slightly different error:

    'The certificate that was used has a trust chain that cannot be verified, The revocation function was unable to check revocation because the revocation server was offline'.

    Checked the MS help, it seems I am very close now, very likely it's a test certificate issue. It might be fixed by some tricks on server side, for now, it's enough. Maybe we don't need it mode at all.

  4. PeerOrChainTrust

    Mixed mode, for tightly controlled enviroments. (Some very restrict Organization have their own root certificates?)

  5. Custom

    Client side creates X509CertificateValidator subclass assigned in app.config:

            <behavior name="myClientBehavior">
              <clientCredentials>
                <serviceCertificate>
                  <authentication certificateValidationMode="Custom"
                                  customCertificateValidatorType="Premotion.Services.MyX509Validator,client" />
                </serviceCertificate>
    
                <!--
                in B2B bindings, add 
    
                  <clientCertificate
                    findValue          = "MyClientCert"
                    storeLocation      = "LocalMachine"
                    storeName          = "My"
                    x509FindType       = "FindBySubjectName"
                  </clientCertificate>
    
                -->
              </clientCredentials>
            </behavior>
    

    same as the example shown in http://www.devatwork.nl/index.php/2007/05/31/wcf-username-authentication

A post about Fundamentals of WCF Security.