Saturday, February 26, 2011

WCF Security: NetTcpBinding with custom UserName/Password authentication

I've been thinking for some time about getting a basic working example of WCF Security using netTcpBinding with custom UserName authentication.  There seems to be plenty of examples using Http flavoured bindings all over the net, but not many for netTcpBinding.

By default WCF will always pass through windows credentials if calling from a .Net based client. (I'm not sure what happens in Silverlight, it would be interesting to know).  In a perfect world every user has their own AD user and they user it to log into their Windows PC.  They know keeping the password safe and secure is important.  Unfortunately we don't live in a perfect world.  Some companies have one AD log-on per job role, especially if that role is contract or seasonal based.  So this effectively means using Windows authentication for a WCF service in this scenario won't work.

I need to be able to identify an incoming call into a service using a custom authentication credential, something the software application is in control of.  The AD System Administrator may be too lazy to create a log-on per user, but to use any software application that requires authentication and authorization, laziness doesn't cut it.

Here's a working example of using netTcpBinding that uses "UserName" authentication.
Download the code here.

The most exciting part is the app.config of the service:

<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        <services>
            <service
                name="Service.CalculatorService"
                behaviorConfiguration="CalculatorServiceBehavior">
                <endpoint
                    binding="netTcpBinding"
                    bindingConfiguration="Binding1"
                    contract="Service.ICalculator"
                    name="NetTcpEndpoint">
                    <identity>
                        <dns value="TestKeyAuthentication" /> <!-- This matches the x509 certificate name -->
                    </identity>
                </endpoint>
                <endpoint
                    address="mex"
                    binding="mexTcpBinding"
                    name="NetTcpMetadataPoint"
                    contract="IMetadataExchange" />
                <host>
                    <baseAddresses>
                        <add baseAddress="net.tcp://localhost:9000/service/UserNamePasswordValidator"/>
                    </baseAddresses>
                </host>
            </service>
        </services>

        <bindings>
            <netTcpBinding>
                <binding name="Binding1">
                    <security mode="Message">
                        <message clientCredentialType="UserName"/>
                    </security>
                </binding>
            </netTcpBinding>
        </bindings>

        <behaviors>
            <serviceBehaviors>
                <behavior name="CalculatorServiceBehavior">
                    <serviceMetadata httpGetEnabled="false"/>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                    <serviceCredentials>
                        <!-- 
                        The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.                  
                        -->
                        <userNameAuthentication
                            userNamePasswordValidationMode="Custom"
                            customUserNamePasswordValidatorType="Service.CustomUserNameValidator, Service"/>
                        <!-- 
                        The serviceCredentials behavior allows one to define a service certificate.
                        A service certificate is used by a client to authenticate the service and provide message protection.
                        This configuration references the "localhost" certificate installed during the setup instructions.
                        -->
                        <serviceCertificate
                            findValue="TestKeyAuthentication"
                            storeLocation="LocalMachine"
                            storeName="AuthRoot"
                            x509FindType="FindBySubjectName"/>
                        <clientCertificate>
                            <certificate
                                findValue="TestKeyAuthentication"
                                storeLocation="LocalMachine"
                                storeName="AuthRoot"
                                x509FindType="FindBySubjectName"/>
                        </clientCertificate>

                    </serviceCredentials>
                </behavior>
            </serviceBehaviors>
        </behaviors>

    </system.serviceModel>
</configuration>

There's a couple of things of note:
The endpoints and service declaration itself is stock standard, apart from the identity element.  This tells the service to use the x509 certificate as its identity. This effectively means that something else cannot spoof the service, the client will know because the spoof will not have the x509 certificate. The text in this element should match the certificate name. Read more about certificates in another post here.

In Binding declaration you can specify the security mode. By default I believe this defaults to "Transport". Tcp/Ip does not have UserName/Password style transport layer security like Http has, so "Message" is your only other option. It does allow you to use "Transport" as an option but this relies completely on certificates for authentication. Not a reasonable demand I can place on my service users.  Interestingly there is no support for Message security in netNamedPipeBinding, which I found quite surprising, I can't see why this shouldn't be allowed.
[Edit: This from Juval Lowy's Programming WCF Book: "Note that netNamedPipeBinding only supports None and Transport security- there is no sense in using Message security over IPC, since with IPC you have exactly one hop from client to service." Message security is intended to provide a secure transmission over an unsecured transport like Tcp/Ip or Http etc.]

The real magic happens in the Binding Configuration. It has an element "serviceCredentials" which allows you to customise the mechanism that authenticates incoming calls.  The element to focus on is "userNameAuthentication". This points to a public creatable type that inherits System.IdentityModel.Selectors.UserNamePasswordValidator.  This class has a method you must override call Validate(string userName, string password).  To deny users access you throw a FaultException.


namespace Service
{
    using System;
    using System.ServiceModel;

    public class CustomUserNameValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }

            var authentication = new Authentication();
            var succeeded = authentication.LogOnUser(userName, password);

            if (succeeded <= 0)
            {
                throw new FaultException("Unknown Username or Incorrect Password");
            }
        }
    }
}


Lastly, here is the certificate declarations.  The "serviceCertificate" element causes WCF to go to the certificate store and find a certificate to use service-side. This prevents malicious software from spoofing the service.  It also allows encryption of all data sent client to server and back to ensure any intermediary cannot sniff the data and see username's password's etc.  Without certificates this would create a significant security flaw, intercepting an examining the binary payload of a tcp/ip packet is not that difficult.  The "clientCertificate" element defines the certificate the client will be using to allow duplex channels to be encrypted. If your service has duplex contracts you need to specify the certificate up front. In a normal request response scenario the client attaches their certificate to all requests and the service has it to encrypt its response. In a duplex callback scenario there is no request, so the service needs to have the client's certificate stored in advance.  In my example here all clients will use the same certificate.

My intention is to generate my own x509 certificates and include them in an installer package to be registered with the local machine certificate store during install.

The great thing about WCF security model is that it is disconnected from your service implementation. Changing the service authentication mode at a later stage is as simple as changing the app.config, no code to change.

When a service call is received by your service contract implementation, you can retrieve the user like this:

var x = ServiceSecurityContext.Current.PrimaryIdentity;


Unfortunately, this only gives you a GenericIdentity object which contains simple the UserName, and Password as strings.  In a future enhancement to this post I'll show how to add more payload data.

To make a call to a secured service you now need to pass in credentials with each invocation, like this:

var proxy = new Service.CalculatorClient();
            if (proxy.ClientCredentials == null)
            {
                throw new NotSupportedException("Client Credentials are null.");
            }

            proxy.ClientCredentials.UserName.UserName = username;
            proxy.ClientCredentials.UserName.Password = password;


Thanks to Marjorie for helping me prepare this. :-)

References.

5 comments:

  1. Hi, if I want to use Impersonation and use custom authentication, how should I proceed ?

    e-mail: brunodeoliveira.22.10@gmail.com

    tks.

    ReplyDelete
  2. Downloading the sample code has for some reason been made a little more difficult by the Google Docs team. When you click the download sample code link you must log into Google Docs, and then use the File menu and choose the Download menu item and the bottom of the menu to download the zip file.

    ReplyDelete
  3. I've changed the code download to sky drive which has fewer issues than Google docs. This should provide easier file downloads. Disregard my previous comment.

    ReplyDelete
  4. In regard to impersonation and UserName security:

    Here's a link about Impersonation http://msdn.microsoft.com/en-us/library/ms730088.aspx.

    Impersonation is designed to be used with Windows credentials that reside on the host server (or the Active Directory to which it belongs). I haven't seen a good solution or a valid reason to do it that combines Impersonation with custom UserName security. This would require you to create a fake identity for WCF to impersonate on the fly if it does not map to a Windows account. It doesn't sound like a good design to me. You can of course access the username and password provided with UserName security server-side and cache them to use throughout an operation call and not have to use impersonation on the thread.

    ReplyDelete