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.
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.
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.
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
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.
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
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="".
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.
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.
6) Next the WCF Service grants permission to continue.
7) The WCF client can now call the service method.
8) The WCF service responds
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)
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.
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