0

What I'm trying to achieve is passing credentials/token to WCF services in every requests. BTW, this credential IS NOT windows credentials, they are fetched from custom db, and the authentication logic is quite simple, tenantId+username+password.

I'm currently using message inspector to insert these kind of information in the headers and fetch them from server-side inspector(using OperationContext).

But in order to stay thread-safe,I have to wrap the requests in every winform request like this:

using (new OperationContextScope((WcfService as ServiceClient).InnerChannel))
{
   MessageHeader hdXXId = MessageHeader.CreateHeader("XXId", "CustomHeader", WinformSomeVariable.XXId);
   OperationContext.Current.OutgoingMessageHeaders.Add(hdXXId);

   _objXX = WcfService.GetXXById(id);

}

Like showed above, this is quite heavy and obviously not a smart way to handle this situation. So is there any way to hold these kind of information safely and can as well fetch them in the WCF Inspectors?

Many thanks!

PS. Thanks to @Abraham Qian, I was being silly the whole time. Just put the client inspector within the same winform project will solve this issue.

Spencer
  • 177
  • 3
  • 20
  • Did you see this question? https://stackoverflow.com/questions/35223163/pass-windows-credentials-to-remote-https-wcf-service – MohammadReza Hasanzadeh Mar 05 '19 at 14:55
  • So how do you achieve custom username/password validation? if possible, why not use the following way to send client credential. ChannelFactory factory = new ChannelFactory(binding, new EndpointAddress("http://localhost:11011")); factory.Credentials.UserName.UserName = "jack"; factory.Credentials.UserName.Password = "123456"; Or client proxy class way, client.ClientCredentials.UserName.UserName = "jack"; client.ClientCredentials.UserName.Password = "123456"; – Abraham Qian Mar 06 '19 at 09:21
  • @MohammadRezaHasanzadeh I'm not using windows authentication. – Spencer Mar 06 '19 at 15:34
  • @AbrahamQian, Thanks for your suggestion. The user authentication is quite simple, just verify user tenantId+username+password. But using ChannelFactory only provides username and password, how can I pass tenantId as well? – Spencer Mar 06 '19 at 15:35
  • It depends on your validation design. I would like that you could share more details about the authentication process of server side. In addition, Using the OperationContextScope to add the provisional message header is available in the current request instead of all the client request. Do you have considered using the IclientMessageInspector interface to create a persistent message header, or redesign authentication using address headers with a custom username/password authentication? – Abraham Qian Mar 07 '19 at 05:53
  • What is the difference between the tenantId field and the username field, if it was't for the identification and protection of the service, I think these fields could be merged. – Abraham Qian Mar 07 '19 at 06:17
  • @AbrahamQian, in this application, different tenant may have same username, so to validate the user, tenantId+username+password is required. I have tried using ClientMessageInspector, but I don't know how to pass these information from winform to the client inspector. The credentials are initially fetched from winform, but the clientMessageInspector resides in another project, so I was quite confused how to pass these credentials to the inspector. – Spencer Mar 07 '19 at 07:09

1 Answers1

0

Just ignore the question of how to refactor your authentication for a moment. As for how to use the IClientMessageInspector interface to create a persistent message header, the following code snippet might be useful (Assume that invocation by using Channel Factory)

class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:1300");
            IService service = ChannelFactory<IService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(uri));
            try
            {
                Console.WriteLine(service.SayHello());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }

    [ServiceContract(Namespace = "mydomain")]
    [CustomContractBehavior]
    public interface IService
    {
        [OperationContract]
        string SayHello();
    }

    public class ClientMessageLogger : IClientMessageInspector
    {
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            string displayText = $"the client has received the reply:\n{reply}\n";
            Console.Write(displayText);
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
        //Add custom message header
            request.Headers.Add(MessageHeader.CreateHeader("myheader","mynamespace",2000));
            string displayText = $"the client send request message:\n{request}\n";
            Console.WriteLine(displayText);
            return null;

        }
    }

    public class CustomContractBehaviorAttribute : Attribute, IContractBehavior, IContractBehaviorAttribute
    {
        public Type TargetContract => typeof(IService);

        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            return;
        }

        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new ClientMessageLogger());
        }

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            return;
        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
        {
            return;
        }
    }
Abraham Qian
  • 7,117
  • 1
  • 8
  • 22
  • @Abrahan Qian, thanks for your suggestion. I have actually implemented the same design as you showed above. But this still does not solve my issue. Let's say after login, user info is stored in a static class in winform. But the `ClientMessageLogger` file is in the WCF service application project. They are 2 different projects. Now how can I pass the winform credentials to the WCF service? As to your code, this hard coded part `request.Headers.Add(MessageHeader.CreateHeader("myheader","mynamespace",2000));`. How can I pass this 2000 from winform to the inspector? – Spencer Mar 07 '19 at 08:11
  • There is no need to separate the projects, we could pass the parameter by using configuration file. ConfigurationManager.AppSettings.Set("mykey", "11"); var result = ConfigurationManager.AppSettings.GetValues("mykey")[0]; – Abraham Qian Mar 07 '19 at 08:33
  • 1
    Yes! You're absolutely right, I was really being silly.... why not just put the client inspector in the winform project. I tried and it worked! Thanks very much! – Spencer Mar 07 '19 at 08:40