none
WCF FaultCode MustUnderstand = 1 Web service .net 4.0 RRS feed

  • Question

  • How do I change my wcf service to be able to accept mustunderstand = 1? This is a scenario where I have to change the service to be able to accept a request from the client. The client sends mustunderstand =1 in the header.

    The service is configured to use basichttpBinding

    <security mode="TransportWithMessageCredential"> <message clientCredentialType="UserName"/> <transport clientCredentialType="None"></transport> </security>

    Using soap UI I insert the following username token into the header

     <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
             <wsse:UsernameToken wsu:Id="UsernameToken-2684C13EA73A35131015516775308851">
                <wsse:Username>username</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
    
             </wsse:UsernameToken>
          </wsse:Security>

    I can reproduce the issue on soap UI when I insert this token in the wcf service request. This is the error

    <FaultMsgRec>
      <ErrCode>100</ErrCode>
      <ErrCat>Error</ErrCat>
      <ErrDesc>An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.--&gt; The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed.  This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process.  Please ensure that the configuration of the client's binding is consistent with the service's binding. </ErrDesc>
    </FaultMsgRec>

    Since I have control over the wcf service I can go and add ValidateMustUnderstand = false in the service behavior. Just like it is explained in the link https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.description.mustunderstandbehavior.validatemustunderstand?view=netframework-4.7.2

    Once I add this to the service behavior the error disappears. But I don't want to turn off validation on the header especially if its a username, password. What should I do to allow mustunderstand=1? Am I missing something that the service doesn't automatically process mustunderstand=1 by default. I know there is code to be written on the client in order to sent a 0 in the header.

    I am using message contracts in my wcf service not data contract. I understand that for certain properties I can go and add attributes like this link https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.messageheaderattribute.mustunderstand?view=netframework-4.7.2. But I am not adding to any properties. I am just adding it to the first linke in soapenv:mustunderstand=1

    Please help!.

    Thank you


    sunDisplay

    Monday, March 4, 2019 5:24 PM

All replies

  • Hi Sundisplay,

    As far as I know, Adding the service behavior and change the ValidateMustUnderstand property is the only way to disable the Mustunderstand.  As you know, depending on the service configuration, every binding has the fixed SOAP message format.
    By default, the Usernameovertransportbinding has the Mustunderstand element node, what is the situation before adding this node? could  the customer consume the service normally?
    Besides, why do you want to add the custom message header, for authentication? What is the configuration on your server side? I will be appreciated that you could share more details with me.

    Here is related discussion, wish it is useful to you.

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/f1f29779-0121-4499-a2bc-63ffe8025b21/wcf-security-soap-header

    Best Regards

    Abraham

    Tuesday, March 5, 2019 10:48 AM
    Moderator
  • Hello Abraham, Thanks for the reply. I am not trying to consume a service. I am trying to match my service to be able to accept a client request from third party. 

    The service is mine. But the client is more important in this case and they sent this request.

    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    
      <SOAP-ENV:Header>
    
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    
          <wsse:UsernameToken>
    
           
    <wsse:Username>*****</wsse:Username>
    
           
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">***** </wsse:Password>
    
          </wsse:UsernameToken>
    
        </wsse:Security>
    
      </SOAP-ENV:Header>
    
      <SOAP-ENV:Body>
    
        <ImgSrch xmlns="http://jPG/2008">
    
         </ImgSrch>
    
      </SOAP-ENV:Body>
    
    </SOAP-ENV:Envelope>

    What should my service binding be in order to match this client. Currently I am using the following

     
    <basicHttpBinding>	 
      <binding name="httpsCertificateBinding" 
    closeTimeout="01:01:00"  openTimeout="01:01:00" receiveTimeout="01:10:00" sendTimeout="01:01:00"  />	
    <security mode="TransportWithMessageCredential">    
        <message clientCredentialType="UserName"/>     
       <transport clientCredentialType="None"></transport>   
     </security>      
    </binding>
    
    
    
     <serviceBehaviors>
         <behavior name="DefaultBehavior">
          <dataContractSerializer maxItemsInObjectGraph="2147483646" />
              <serviceMetadata httpGetEnabled="true"  />
              <serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
    <serviceCredentials>
           <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" membershipProviderName="CustomMembershipProvider"/>
     </serviceCredentials>
        <useRequestHeadersForMetadataAddress />
          </behavior>
       
            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
          </serviceBehaviors>

    But this binding doesn't accept Mustunderstand =1 and the service is throwing an exception about mustunderstand. So i changed the behavior add added Validatemustunderstand = false. This worked but now the response is not generating a username header. 


    sunDisplay

    Tuesday, March 5, 2019 2:54 PM
  • No help?

    sunDisplay

    Friday, March 8, 2019 6:37 PM
  • @Abraham Qian, 

    Hello Abraham, did you answer the question. I am still having this issue

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/dcf5ca9a-e0da-4562-ad44-826ddcf1e830/wcf-basic-http-binding-with-ssl-username-log-and-mustunderstand?forum=wcf#dcf5ca9a-e0da-4562-ad44-826ddcf1e830

    Please provide some input.


    sunDisplay

    Saturday, March 9, 2019 3:47 AM
  • Hi SunDisplay,

    I haven’t come across a scenario like yours where you try to match the client’s request, and it is impossible that the communication is complete during one-round communication.

    Try to add the following code snippets not to validate the MustUnderstand.

    MustUnderstandBehavior behavior = new MustUnderstandBehavior(false);
                  sh.Description.Endpoints[0].Behaviors.Add(behavior);

    Now I would like that whether the authentication is accomplished. Can the client work properly?
    As far as I know, whether BasicHttpBinding or WsHttpBinding, use TransportWithMessageCredential security mode, during the process of our communication of the SOAP message has MustUnderstand=1 nodes.
    The following is my example, hope useful to you.
    Server(10.157.13.69, Console application)

    class Program
        {
            static void Main(string[] args)
            {
                Uri uri = new Uri("https://localhost:11011");
                //WSHttpBinding binding = new WSHttpBinding();
                //binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
                //binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
                //binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                BasicHttpBinding binding = new BasicHttpBinding();
                binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
                binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
    
                using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
                {
                    sh.AddServiceEndpoint(typeof(IService), binding, "");
                    ServiceMetadataBehavior smb;
                    smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
                    if (smb == null)
                    {
                        smb = new ServiceMetadataBehavior()
                        {
                            HttpsGetEnabled = true
                        };
                        sh.Description.Behaviors.Add(smb);
                    }
                    ServiceCredentials sc;
                    sc = sh.Description.Behaviors.Find<ServiceCredentials>();
    
                    sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
                    sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustUserNamePasswordVal();
                    Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
                    sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
    
                    sh.Opened += delegate
                    {
                        Console.WriteLine("Service is ready");
                    };
                    sh.Closed += delegate
                    {
                        Console.WriteLine("Service is clsoed");
                    };
    
    
                    sh.Open();
    
                    Console.ReadLine();
                    sh.Close();
                    Console.ReadLine();
                }
    
            }
        }
        [ServiceContract]
        public interface IService
        {
            [OperationContract]
            string SayHello();
        }
    
        public class MyService : IService
        {
            public string SayHello()
            {
                return $"Hello Stranger,{DateTime.Now.ToLongTimeString()}";
            }
        }
        internal class CustUserNamePasswordVal : UserNamePasswordValidator
        {
            public override void Validate(string userName, string password)
            {
                if (userName != "jack" || password != "123456")
                {
                    throw new Exception("Username/Password is not correct");
                }
            }
        }


    Binding the certificate to the Port.

    netsh http add sslcert ipport=0.0.0.0:11011 certhash=6e48c590717cb2c61da97346d5901b260e983850 appid={ED4CE60F-6B2E-4EE6-828F-C1A6A1B12565}

    Client end (calling service by adding service reference)

    var client = new ServiceReference1.ServiceClient();
                client.ClientCredentials.UserName.UserName = "jack";
                client.ClientCredentials.UserName.Password = "123456";
                try
                {
                    var result = client.SayHello();
                    Console.WriteLine(result);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }

    App.config(Auto-generated)

        <system.serviceModel>
            <bindings>
                <basicHttpBinding>
                    <binding name="BasicHttpBinding_IService">
                        <security mode="TransportWithMessageCredential" />
                    </binding>
                </basicHttpBinding>
            </bindings>
            <client>
                <endpoint address="https://10.157.13.69:11011/" binding="basicHttpBinding"
                    bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
                    name="BasicHttpBinding_IService" />
            </client>
    </system.serviceModel>


    Result.

    Best Regards
    Abraham


    Monday, March 11, 2019 3:10 AM
    Moderator
  • Hello Abraham, 

    Thank you very much for all that sample code. I am doing the exact same thing except have the bindings in the config and not code. Also the certificate,  I am not too sure if the azure guy configured the certificate to a port. I will check that shortly.

    After struggling with this for more than one week, your code gave me a sense of relief that there is nothing wrong in the code.

    Thank you again


    sunDisplay

    Monday, March 11, 2019 1:42 PM
  • Hello Abraham, 

    I added a lot of logging and this is the difference between working and non working case

    In the working case the validate user of the membershipprovider is called, then AfterReceiveRequest is called and finally the webservice is invoked. 

    But in the non-working case the validateuser is not called. It directly goes on to AfterReceiveRequest and then webservice throws an exception that it can't understand the header. 

     The azure application gateway routes the request to the VM. It somehow looks the header or membership provider is not being recognized. SSL is managed at the gateway level.  Do I need to add the membership provider in any other config file except the web.config may be like a gateway config? 

     <behaviors>
          <serviceBehaviors>
            <behavior name="DefaultBehavior">
               <PfRequestOutputBehaviorExtensionElement />
              <serviceMetadata  httpGetEnabled ="true" httpsGetEnabled ="true"  />
              <serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
              <serviceCredentials>
                <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="IDServices.CustomMembershipProvider,IDServices"/>
              </serviceCredentials>
              <useRequestHeadersForMetadataAddress />

            </behavior>

            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
            <PfRequestOutputBehaviorExtensionElement />
            </behavior>
          </serviceBehaviors>
          <endpointBehaviors>
            <behavior name="endbehavior1">
              <dataContractSerializer maxItemsInObjectGraph="2147483647" />
            </behavior>
          </endpointBehaviors>
        </behaviors>

    Any idea on what causes the Validate user to be invoked. This looks like a configuration problem. 

    Also: As you suggested I did the following. But then the client was complaining that Security processor was unable to find a security header in the message. I tried to manually add headers but thats so intrusive and error prone I feel.

    MustUnderstandBehavior behavior = new MustUnderstandBehavior(false);
                  sh.Description.Endpoints[0].Behaviors.Add(behavior);


    sunDisplay


    • Edited by SunDisplay Monday, March 11, 2019 9:34 PM
    Monday, March 11, 2019 9:08 PM
  • Hi SunDisplay,

    Are you sure you are using MembershipProvider to verify the client credentials? I never actually used MembershipProvider in the project, but as far as I know, we need to configure the MemebershipProvider.

        <membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15">
          <providers>
            <clear />
            <add 
              name="SqlProvider" 
              type="System.Web.Security.SqlMembershipProvider" 
              connectionStringName="MySqlConnection"
              applicationName="MyApplication"
              enablePasswordRetrieval="false"
              enablePasswordReset="true"
              requiresQuestionAndAnswer="true"
              requiresUniqueEmail="true"
              passwordFormat="Hashed" />
          </providers>
    </membership>
      <connectionStrings>
        <add name="MySqlConnection" connectionString="Data 
          Source=MySqlServer;Initial Catalog=aspnetdb;Integrated
          Security=SSPI;" />
      </connectionStrings>
    
    

    And specify the validation mode explicitly.

      <wsHttpBinding>  
      <!-- Set up a binding that uses UserName as the client credential type -->  
        <binding name="MembershipBinding">  
          <security mode ="Message">  
            <message clientCredentialType ="UserName"/>  
          </security>  
        </binding>  
      </wsHttpBinding>  
    


        <serviceCredentials>
                <userNameAuthentication userNamePasswordValidationMode="MembershipProvider" customUserNamePasswordValidatorType="SqlMembershipProvider"/>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
    

    https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-use-the-aspnet-membership-provider
    Feel free to let me know if there is anything I can help with.
    Best Regards
    Abraham

    Tuesday, March 12, 2019 10:27 AM
    Moderator