There was a project that I assisted with the WCF communications where they needed to allow the client to specify different credentials without being dependent on the windows account.
The first thing that comes into mind is to use the UserNameToken technique to pass in the client credentials. The design instructed to use TCP as the transport and not use message security. Obviously, this technique has privacy and integrity issues where there isn’t any encryption nor signing, but that was their decision because it wasn’t an issue in the purpose of the project.
Well, this setting isn’t as trivial as you would expect.
The default form of the NetTcpBinding allows you to use UserNameToken only as part of message security and forces you to use a certificate to enable that message security.
I ended up setting them a CustomBinding which provides the scenario they needed.
Service Side
Configuring the Service Host -
Code Snippet
- _host = new ServiceHost(typeof(Service));
- _host.AddServiceEndpoint(typeof(IService), Config.ServiceBinding, Config.ServiceAddress.Uri.AbsoluteUri);
-
- _host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
- _host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameTokenValidator();
-
- _host.Open();
Custom Simple Validator (you can write any logic you need here) -
Code Snippet
- class CustomUserNameTokenValidator : UserNamePasswordValidator
- {
- public override void Validate(string userName, string password)
- {
- Console.WriteLine("CustomUserNameTokenValidator.Validate - {0} / {1}", userName, password);
-
- if (string.IsNullOrEmpty(userName))
- {
- throw new SecurityTokenValidationException("Invalid username");
- }
- }
- }
In the service code, you can extract the caller’s identity name as follows -
Code Snippet
- class Service : IService
- {
- public void Do()
- {
- Console.WriteLine("Service.Do() - Identity Name: {0}",
- ServiceSecurityContext.Current.PrimaryIdentity.Name);
- }
- }
Client Side
In the client side you need to use the same binding, provide the UserNameToken credentials and simply call the service -
Code Snippet
- ChannelFactory<IService> factory = new ChannelFactory<IService>(Config.ServiceBinding, Config.ServiceAddress);
- factory.Credentials.UserName.UserName = "myUser";
- factory.Credentials.UserName.Password = "myPassword";
-
- IService proxy = factory.CreateChannel();
-
- proxy.Do();
-
- ((ICommunicationObject)proxy).Close();
Configuration
Following is the CustomBinding which enables this scenario -
Code Snippet
- SecurityBindingElement securityElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
- ((TransportSecurityBindingElement)securityElement).AllowInsecureTransport = true;
-
- _serviceBinding = new CustomBinding(new BindingElement[] {
- securityElement,
- new BinaryMessageEncodingBindingElement(),
- new TcpTransportBindingElement()
- });
Feel free to download the source code and see that in action.