Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / WCF

BasicHttpBinding with Custom Authorization

5.00/5 (3 votes)
24 Feb 2015CPOL4 min read 29.8K  
Monitor Network Traffic for BasicHttpBinding WCF call using Wireshark

Introduction

This article demonstrates the network traffic passed between 2 computers when a WCF client calls a WCF service while passing a username/password. I used Wireshark to monitor the network traffic.

Note this is for demonstration purposes. Using BasicHttpBinding like this is NOT secure.

Background

WCF has a myriad of permutations of configurations e.g. bindings, security. The number of permutations can seem overwhelming. This article shows what is happening under the hood when a simple service call is made using BasicHttpBinding.

Using the code

There are 2 projects - one for the WCF service and one for the WCF client.

I connected my 2 PC's together using a crossover cable. I then set up each PC with an IP address. I assigned IP address 192.168.1.10 to the PC hosting the WCF service as below.

Image 1

Image 2

For the client, I assigned the IP address 192.168.1.11

WCF Service

Here is the code for the WCF service. Note the use of the CustomValidator class. I am going to get the WCF client to transmit a username and password to the WCF service and authentication will fail if the password is not = "Secret". Beyond that the code is fairly standard service code but specifies that the CustomValidator is doing the authentication.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.IdentityModel.Selectors;

namespace JoesTestWCFService
{
    public class CustomValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (password != "Secret")
                throw new FaultException("Bad Password " + password);

            return;
        }
    }

    [ServiceContract]
    public interface IJoesService
    {
        [OperationContract]
        string TestMethod(string msg);
    }

    public class JoesService : IJoesService
    {
        Guid serviceId;
        public JoesService()
        {
            serviceId = Guid.NewGuid();
        }
        public string TestMethod(string msg)
        {
            return "Hello " + msg + ": (" + serviceId.ToString() + ")";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Uri[] baseAddress = null;

            baseAddress = new Uri[] { new Uri("http://192.168.1.10:8082/") };
            
            var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);

            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

            ServiceHost host = new ServiceHost(typeof(JoesService), baseAddress);

            host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
            host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomValidator();

            host.AddServiceEndpoint(typeof(IJoesService), binding, baseAddress.First());

            host.Open();

            Console.WriteLine("Ready ...");
            Console.ReadLine();
        }
    }
}

WCF Client

Here is the code for the WCF client. Note I pass a username/password when calling the WCF service.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace JoesTestWCFClient
{
    [ServiceContract]
    public interface IJoesService
    {
        [OperationContract]
        string TestMethod(string msg);
    }

    class Program
    {
        static void Main(string[] args)
        {
            string uri = null;
            EndpointAddress endPA;

            uri = "http://192.168.1.10:8082";
            var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

            endPA = new EndpointAddress(uri);
            ChannelFactory<IJoesService> factory = new ChannelFactory<IJoesService>(binding, endPA);

            factory.Credentials.UserName.UserName = "JJJOE";
            factory.Credentials.UserName.Password = args[0];

            IJoesService proxy = factory.CreateChannel();

            string input;
            do
            {
                Console.WriteLine(proxy.TestMethod("JOEJOEJOEJOEJOE"));

                input = Console.ReadLine();
            } while (input != "x");
        }
    }
}

Network Traffic

Here is all the traffic for one call from the WCF client to the WCF service

Image 3

1) The first 3 lines are just setting up the TCP connection. Note that although Http is seen as "connectionless", under the hood a TCP connection is established for the duration of the call.

Note the port (49569) for the client. This wasn't assigned by me. The destination port (8082) was though.

Image 4

2) In the next line, the WCF client makes a request on the WCF service i.e. TestMethod on IJoesService on host 192.168.1.10 - port 8082

Image 5

4) The next line (41) is a Challenge by the WCF Service because request was unauthenticated.

see http://www.ietf.org/rfc/rfc2617.txt?number=2617

The "basic" authentication scheme is based on the model that the client must authenticate itself with a user-ID and a password for each realm.  The realm value should be considered an opaque string which can only be compared for equality with other realms on that server. The server will service the request only if it can validate the user-ID and password for the protection space of the Request-URI.

   There are no optional authentication parameters.

   For Basic, the framework above is utilized as follows:

      challenge   = "Basic" realm

      credentials = "Basic" basic-credentials

   Upon receipt of an unauthorized request for a URI within the protection space, the origin server MAY respond with a challenge like the following:

      WWW-Authenticate: Basic realm="WallyWorld"

so here is the challenge from my WCF service

WWW-Authenticate: Basic realm="".

Image 6

5) In the line (42), the WCF client responds to the Challenge by the WCF service by passing Credentials.

To receive authorization, the client sends the userid and password,
   separated by a single colon (":") character, within a base64 [7]
   encoded string in the credentials.
 
      basic-credentials = base64-user-pass
      base64-user-pass  = <base64 [4] encoding of user-pass,

If the user agent wishes to send the userid "Aladdin" and password
   "open sesame", it would use the following header field:
 
      Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Note the credentials are NOT encypted - they are encoded. It is straightforward to extract the password as I show next.

Image 7

You can see from Wireshark that the client has passed

Authorization: Basic SkpKT0U6U2VjcmV0

You can use the site such as http://string-functions.com/base64decode.aspx to decode this.

Image 8

6) Next the WCF Service grants permission to continue.

Image 9

7) The WCF client can now call the service method.

Image 10

8) The WCF service responds

Image 11

Points of Interest

1) Obviously this method is not secure as it is straightforward to decode the username and password if it is intercepted.

A more secure method to pass the username and password would use a more secure binding e.g. netTcpBinding. This is a little more complex to set up. One method I have used is to set up a self certified certificate on the WCF service PC. The public key of this certificate is sent from the WCF service to the WCF client so that the client can use it to encrypt credentials.

Below is a snapshot of Wireshark traffic where I have used netTcpBinding. You can see the WCF service passing the public key of it's certificate to client (I've circled the start of the public key in the traffic - you can see it matches the Certificate key in the screen to the right)

Image 12

2) Repeated calls to the BasicHttpBinding version of the code above is PerCall by default i.e. there is a fresh instance of JoesService called each time TestMethod is called. You will see a different ServiceId printed on each method call.

Image 13

If you want a Per-session for your client you will need to use another binding such as netTcpBinding.

 

All the above is for demo purposes only, to hopefully help demystify what happens under the hood when WCF makes calls between PCs. Please do your own research to establish the appropriate security model you need in your own projects.

History

v1.0 Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)