Pages

Monday, 23 April 2012

Custom WCF Authentication Using Message Contract

When you create a WCF Service or Web Service, you may want to implement subscription based access to this service. This allows you to have many customers consuming the service, with each customer having their own account and credentials to access.

When creating ASP.NET Web Services, implementing subscription based access can be done by using the SOAP header to add credentials particular to the customer consuming the web service. In a WCF service, this is done using the Message Contract.

For this post, I have created a test WCF service application which is used to return account data for a customer. The interface has one operations contract called GetAccountsData which returns a message contract called accounts info. As the accounts info message contract has sensitive information, I am also specifying the EncryptAndSign attribution on the properties on the message contract. 

The Interface:

[ServiceContract]
    public interface IAccountsService
    {
        [OperationContract]
        AccountsInfo GetAccountsData(CustomerCredential cCredential);
    }


The message contract:

    [MessageContract]
    public class AccountsInfo
    {
        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string CustomerID;

        [MessageBodyMember(Order = 2, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string AccountNumber;

        [MessageBodyMember(Order = 3, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public double CurrentBalance;
    }

You'll notice the use of another message contract in the GetAccountsData method - CustomerCredential. This method contract is used to pass authentication information from the client when GetAccountsData is called.

    [MessageContract]
    public class CustomerCredential
    {
        [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string SecurityToken;

        [MessageBodyMember(Order = 1, ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string CustomerID;

    }

I also have a class that implements IAccountsService and contains GetAccountsData. For the purposes of this example, I have hard-coded an authentication token and have continued if the correct token is passed through, however this is where you would make a call to your data store to authenticate the token and validate the client accessing the service.

public AccountsInfo GetAccountsData(CustomerCredential credentials)
        {
            if (!string.IsNullOrEmpty(credentials.SecurityToken))
            {
                if (!credentials.SecurityToken.Equals("7DB8B334-8F1A-4EF0-8586-1FF7C279502F"))
                {
                    throw new FaultException("You are not authorised to access the method");
                }
                else
                {
                    // A database/data store call would be made here to get the data and return based on the customer ID.
                    // You could also do further security checks such as the customer requiring to enter a PIN
                    if (credentials.CustomerID.Equals("C123"))
                    {
                        return new AccountsInfo { CustomerID = "C123", AccountNumber = "987654321", CurrentBalance = 1000 };
                    }
                    else
                    {
                        return new AccountsInfo { CustomerID = "N/A", AccountNumber = "N/A", CurrentBalance = 0 };
                    }
                }
            }
            else
            {
                throw new FaultException("Security information not provided");
            }
        }


To consume this service, I have created an empty MVC3 Web Application which has just one controller returning an action result - I didn't want to overcomplicate for the example, but you should be able to see how this example can be extended and built upon.

The Index ActionResult of my HomeController has the following code:

CustomerAccount account = new CustomerAccount();
            try
            {
                ChannelFactory<IAccountsService> factory = new ChannelFactory<IAccountsService>("WSHttpBinding_IAccountsService", new EndpointAddress("http://localhost:8013/AccountsService.svc"));
                IAccountsService proxy = factory.CreateChannel();
                CustomerCredential credentials = new CustomerCredential();
                credentials.SecurityToken = "7DB8B334-8F1A-4EF0-8586-1FF7C279502F";
                credentials.CustomerID = "C123";
                AccountsInfo accountDetails = proxy.GetAccountsData(credentials);
                
                account.CustomerID = accountDetails.CustomerID;
                account.AccountNumber = accountDetails.AccountNumber;
                account.CurrentBalance = accountDetails.CurrentBalance;

            }
            catch (Exception ex)
            {
                throw ex;
            }

The CustomerAccount is my model, and I am calling my WCF service to return data in the model which is passed to the view.

You'll notice the endpoint of http://localhost:8013 - this is where I published my WCF service and the address I used to add the service reference to my MVC3 application. If you download the attached sample, be sure to either change references to http://localhost:8013 to the URL you published the WCF service to.

To pass in the credentials, I am creating a CustomerCredential object, which we defined in the service. Here I am passing in my hard coded token, however in reality you would issue a token to each client consuming your service for them to pass in at this stage. Your service would then validate the token used as described above.

The test WCF service and MVC3 Web Application used for this post are available from here - www.web-architecture.com/files/WCFSample.zip




1 comment:

  1. The strengths of this type of screen are that they actually look brighter and sharper than resistive screen as they are glass not plastic and that they register touch very quickly and easily so there’s no need for a stylus.
    Buy League of Legends Account

    LOL Boost

    ReplyDelete