title: How to properly generate security header for SOAP request in C#?
body: <p>I'm trying to consume a SOAP service using C#, but I'm struggling with Outgoing WS-Security configuration.</p>
  <p>What I've done so far:</p>
  <ol>
  <li>Created a service reference in VS 2019 and extended the generated Client class to pass my configuration in there</li>
  <li>Created a custom endpoint behavior:</li>
  </ol>
  <pre><code>
      this.ChannelFactory.Endpoint.EndpointBehaviors.Add(new WsSecurityEndpointBehavior(username, password, timeout));
      // ...
  
      public class WsSecurityEndpointBehavior : IEndpointBehavior
      {
          private readonly string _password;
          private readonly TimeSpan _timeout;
          private readonly string _username;
      
          public WsSecurityEndpointBehavior(string username, string password, TimeSpan timeout)
          {
              this._username = username;
              this._password = password;
              this._timeout = timeout;
          }
      
          void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint,
              BindingParameterCollection bindingParameters)
          {
          }
      
          void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
              clientRuntime.ClientMessageInspectors.Add(new WsSecurityMessageInspector(_username, _password, _timeout));
          }
          void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
          }
      
          void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
          {
          }
      }
  
  </code></pre>
  <ol start="3">
  <li>Created a ClientMessageInspector:</li>
  </ol>
  <pre><code>
      public class WsSecurityMessageInspector : IClientMessageInspector
      {
          private readonly string _password;
          private readonly TimeSpan _timeout;
          private readonly string _username;
      
          public WsSecurityMessageInspector(string username, string password, TimeSpan timeout)
          {
              this._username = username;
              this._password = password;
              this._timeout = timeout;
          }
      
          object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
          {
              var header = new Security
              {
                  UsernameToken =
                  {
                      Password = new Password
                      {
                          Value = _password,
                          Type =
                              &quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest&quot;
                      },
                      Username = _username
                  },
                  Timestamp =
                  {
                      Created = DateTime.UtcNow.ToString(&quot;yyyy-MM-ddThh:mm:ss.fffZ&quot;),
                      Expires = DateTime.UtcNow.Add(_timeout).ToString(&quot;yyyy-MM-ddThh:mm:ss.fffZ&quot;)
                  }
              };
              request.Headers.Add(header);
              return null;
          }
      
          void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
          {
          }
  
  }
  </code></pre>
  <ol start="4">
  <li>Added poco classes for xml fields:</li>
  </ol>
  <pre><code>
      public class Password
      {
          [XmlAttribute] public string Type { get; set; }
          [XmlText] public string Value { get; set; }
      }
      
      public class UsernameToken
      {
          [XmlElement] public string Username { get; set; }
          [XmlElement] public Password Password { get; set; }
      }
      
      public class Timestamp
      {
          [XmlElement] public string Created { get; set; }
          [XmlElement] public string Expires { get; set; }
      }
  
  </code></pre>
  <ol start="5">
  <li>And finally implemented a Security message header:</li>
  </ol>
  <pre><code>
      public class Security : MessageHeader
      {
          public Security()
          {
              UsernameToken = new UsernameToken();
              Timestamp = new Timestamp();
          }
      
          public UsernameToken UsernameToken { get; set; }
          public Timestamp Timestamp { get; set; }
      
          public const string SoapEnvelopeNamespace = &quot;http://schemas.xmlsoap.org/soap/envelope/&quot;;
          public const string WsseUtilityNamespaceUrl = &quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd&quot;;
          public const string WsseNamespace = &quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd&quot;;
      
      
          public override string Name =&gt; GetType().Name;
      
          public override string Namespace =&gt; WsseNamespace;
      
          public override bool MustUnderstand =&gt; true;
      
          protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
          {
              writer.WriteStartElement(&quot;wsse&quot;, Name, Namespace);
              //writer.WriteAttributeString(&quot;s&quot;, &quot;mustUnderstand&quot;, SoapEnvelopeNamespace, &quot;1&quot;); // - doesn't work either way
      
              writer.WriteXmlnsAttribute(&quot;wsse&quot;, Namespace);
              writer.WriteXmlnsAttribute(&quot;wsu&quot;, WsseUtilityNamespaceUrl);
          }
      
          protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
          {
              // Timestamp
              writer.WriteStartElement(&quot;wsu&quot;, &quot;Timestamp&quot;, WsseUtilityNamespaceUrl);
      
              writer.WriteAttributeString(&quot;wsu&quot;, &quot;Id&quot;, WsseUtilityNamespaceUrl, &quot;TS-&quot; + Guid.NewGuid());
      
              writer.WriteStartElement(&quot;wsu&quot;, &quot;Created&quot;, WsseUtilityNamespaceUrl);
              writer.WriteValue(Timestamp.Created);
              writer.WriteEndElement();
      
              writer.WriteStartElement(&quot;wsu&quot;, &quot;Expires&quot;, WsseUtilityNamespaceUrl);
              writer.WriteValue(Timestamp.Expires);
              writer.WriteEndElement();
      
              writer.WriteEndElement(); // Timestamp
      
              // username token
              writer.WriteStartElement(&quot;wsse&quot;, &quot;UsernameToken&quot;, Namespace);
              writer.WriteAttributeString(&quot;wsu&quot;, &quot;Id&quot;, &quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd&quot;, &quot;UsernameToken-&quot; + Guid.NewGuid());
              // Username
              writer.WriteStartElement(&quot;wsse&quot;, &quot;Username&quot;, Namespace);
              writer.WriteValue(UsernameToken.Username);
              writer.WriteEndElement();
      
              var nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));
              var createdStr = Timestamp.Created;
              var pwd = GetSHA1String(nonce + createdStr + UsernameToken.Password.Value);
      
              // Password
              writer.WriteStartElement(&quot;wsse&quot;, &quot;Password&quot;, Namespace);
              writer.WriteAttributeString(&quot;Type&quot;, UsernameToken.Password.Type); //PasswordDigest
              writer.WriteValue(pwd);
              writer.WriteEndElement();
      
              writer.WriteStartElement(&quot;wsse&quot;, &quot;Nonce&quot;, Namespace);
              writer.WriteAttributeString(&quot;EncodingType&quot;, &quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary&quot;);
              writer.WriteValue(nonce);
              writer.WriteEndElement();
      
              writer.WriteStartElement(&quot;wsu&quot;, &quot;Created&quot;, WsseUtilityNamespaceUrl);
              writer.WriteValue(createdStr);
              writer.WriteEndElement();
      
              // username token
              writer.WriteEndElement();
          }
      
          private static string GetSHA1String(string phrase)
          {
              var sha1Hasher = new SHA1CryptoServiceProvider();
              var hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
              return Convert.ToBase64String(hashedDataBytes);
          }
      }
  
  </code></pre>
  <p>And the XML example I have in the result:</p>
  <pre><code>
      &lt;wsse:Security xmlns:wsse=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd&quot;
          xmlns:wsu=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd&quot;&gt;
          &lt;wsu:Timestamp wsu:Id=&quot;TS-2b7092f4-8f62-40b5-8f7e-ab774ef967aa&quot;&gt;
              &lt;wsu:Created&gt;2021-05-17T10:20:16.913Z&lt;/wsu:Created&gt;
              &lt;wsu:Expires&gt;2021-05-17T10:25:16.913Z&lt;/wsu:Expires&gt;
          &lt;/wsu:Timestamp&gt;
          &lt;wsse:UsernameToken wsu:Id=&quot;UsernameToken-8ee97b0e-cbdf-465b-97b0-8e5194f2f4a9&quot;&gt;
              &lt;wsse:Username&gt;username&lt;/wsse:Username&gt;
              &lt;wsse:Password Type=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest&quot;&gt;base64_described_above&lt;/wsse:Password&gt;
              &lt;wsse:Nonce EncodingType=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary&quot;&gt;MGNiNDUxMTYtYjBlMy00ZmQzLTgwYTUtODgxZGY2NmUyMDI0&lt;/wsse:Nonce&gt;
              &lt;wsu:Created&gt;2021-05-17T10:20:16.913Z&lt;/wsu:Created&gt;
          &lt;/wsse:UsernameToken&gt;
      &lt;/wsse:Security&gt;
  
  </code></pre>
  <p>I was able to make it work in Soap UI, but with this C# implementation I'm getting <code>ns1:SecurityError</code>. What am I doing wrong?</p>
  <p>Thanks in advance.</p>
  <p>---- <strong>UPDATE</strong> ----
  The full request XML:</p>
  <pre><code>
      &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;
      &lt;s:Envelope xmlns:s=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
          &lt;s:Header&gt;
              &lt;Action s:mustUnderstand=&quot;1&quot;
                  xmlns=&quot;http://schemas.microsoft.com/ws/2005/05/addressing/none&quot;&gt;[ActionICall]&lt;/Action&gt;
              &lt;h:RequestContext xmlns=&quot;http://hidden-for-security/webservices/v1/messages&quot;
                  xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
                  xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot;
                  xmlns:h=&quot;http://hidden-for-security/webservices/v1/messages&quot;&gt;
                  &lt;Id&gt;f7ed31dc-b7ad-4a5d-ae17-c86663bbb2da&lt;/Id&gt;
                  &lt;Language&gt;en&lt;/Language&gt;
              &lt;/h:RequestContext&gt;
              &lt;wsse:Security xmlns:wsse=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd&quot;
                  xmlns:wsu=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd&quot;&gt;
                  &lt;wsu:Timestamp wsu:Id=&quot;TS-ceb077e4-3255-405b-aac7-d7ae9bf1b884&quot;&gt;
                      &lt;wsu:Created&gt;2021-05-17T12:01:11.852Z&lt;/wsu:Created&gt;
                      &lt;wsu:Expires&gt;2021-05-17T12:06:11.852Z&lt;/wsu:Expires&gt;
                  &lt;/wsu:Timestamp&gt;
                  &lt;wsse:UsernameToken wsu:Id=&quot;UsernameToken-1373fa47-6f2a-4d44-87d5-5270132f9830&quot;&gt;
                      &lt;wsse:Username&gt;username&lt;/wsse:Username&gt;
                      &lt;wsse:Password Type=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest&quot;&gt;calculated-password-in-base64&lt;/wsse:Password&gt;
                      &lt;wsse:Nonce EncodingType=&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary&quot;&gt;MjkxY2VkY2MtZjA4ZC00NmQ0LWFkMjUtYWQ4ZGU3ODFmNGVm&lt;/wsse:Nonce&gt;
                      &lt;wsu:Created&gt;2021-05-17T12:01:11.852Z&lt;/wsu:Created&gt;
                  &lt;/wsse:UsernameToken&gt;
              &lt;/wsse:Security&gt;
          &lt;/s:Header&gt;
          &lt;s:Body xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
              xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot;&gt;
              &lt;ActionICallRequest xmlns=&quot;http://hidden-for-security/webservices/v1/messages&quot;&gt;
                  &lt;UniqueNumber xmlns=&quot;http://hidden-for-security/webservices/v1/datamodel&quot;&gt;659648795&lt;/UniqueNumber&gt;
              &lt;/ActionICallRequest&gt;
          &lt;/s:Body&gt;
      &lt;/s:Envelope&gt;
  
  </code></pre>
  <p>The response I get:</p>
  <pre><code>
      &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;
      &lt;soap:Envelope xmlns:soap=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;
        &lt;s:Header xmlns:s=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot; /&gt;
        &lt;soap:Body&gt;
          &lt;soap:Fault&gt;
            &lt;faultcode xmlns:ns1=&quot;http://ws.apache.org/wss4j&quot;&gt;ns1:SecurityError&lt;/faultcode&gt;
            &lt;faultstring&gt;A security error was encountered when verifying the message&lt;/faultstring&gt;
          &lt;/soap:Fault&gt;
        &lt;/soap:Body&gt;
      &lt;/soap:Envelope&gt;
  
  </code></pre>