62

I am attempting to consume a web service through its corresponding wsdl. This service is dependent upon authentication conforming to Web Services Security Basic Security Profile 1.0 including that the correct xmls namespace of http://docs.oasis-open.org/wss/2004/01/oasis-200401wss-wssecurity-secext-1.0.xsd must be included in the request.

Example:

<wsse:UsernameToken xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' >
   <wsse:Username>
      Bob
   </wsse:Username>
   <wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>
      1234
   </wsse:Password>
</wsse:UsernameToken>

My first attempts were along the lines of Add Service Reference targeting the wsdl and from the generated proxies using them as such

ServicePointManager.ServerCertificateValidationCallback = 
    (object s, X509Certificate certificate, X509Chain chain,
                     SslPolicyErrors sslPolicyErrors) => true;

var basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = 
                                                HttpClientCredentialType.Basic;

var endpoint = new EndpointAddress("https://secure-ausomxana.crmondemand.com/..."

using (var client = new ContactClient(basicHttpBinding, endpoint))
{

    var credential = client.ClientCredentials.UserName;
    credential.UserName = "bob";
    credential.Password = "1234";

    var input = ...    
    var output = client.ContactQueryPage(input);
}

However attempting to interrogate the SOAP messages with Fiddler I see that no UsernameToken element has been added.

What is the correct way to fulfill this contract?

Edit: following the response from @John Saunders I attempted to alter my code to use a wsHttpBinding

var wsHttpBinding = new WSHttpBinding(SecurityMode.Transport);
wsHttpBinding.Security.Transport.ClientCredentialType =
                                         HttpClientCredentialType.Basic;

Using this binding the SOAP message becomes

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">document/urn:crmondemand/ws/ecbs/contact/10/2004:ContactQueryPage</a:Action>
    <a:MessageID>urn:uuid:17807f44-1fcasfdsfd</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">https://secure-ausomxana.crmondemand.com/Services/Integration</a:To>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ContactQueryPage_Input xmlns="urn:crmondemand/ws/ecbs/contact/10/2004">
      <ListOfContact xmlns="urn:/crmondemand/xml/Contact/Query">
        <Contact>
          <Id>1-asdfd</Id>
        </Contact>
      </ListOfContact>
    </ContactQueryPage_Input>
  </s:Body>
</s:Envelope>

This adds the Header element, as opposed to the wsse:UsernameToken element for reference the original soap message using the BasicHttpBinding is

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ContactQueryPage_Input xmlns="urn:crmondemand/ws/ecbs/contact/10/2004">
      <ListOfContact xmlns="urn:/crmondemand/xml/Contact/Query">
        <Contact>
          <Id>1-asdfds</Id>
        </Contact>
      </ListOfContact>
    </ContactQueryPage_Input>
  </s:Body>
</s:Envelope>

If I change the binding to be

var wsHttpBinding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
wsHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
wsHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

The SOAP message I get out is

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT</a:Action>
    <a:MessageID>urn:uuid:eeb75457-f29e-4c65-b4bf-b580da26e0c5</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">https://secure-ausomxana.crmondemand.com/Services/Integration</a:To>
    <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" s:mustUnderstand="1">
      <u:Timestamp u:Id="_0">
        <u:Created>2011-05-02T13:30:09.360Z</u:Created>
        <u:Expires>2011-05-02T13:35:09.360Z</u:Expires>
      </u:Timestamp>
      <o:UsernameToken u:Id="uuid-dc3605a0-6878-42f4-b1f2-37d5c04ed7b4-2">
        <o:Username>Bob</o:Username>
        <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">1234</o:Password>
      </o:UsernameToken>
    </o:Security>
  </s:Header>
  <s:Body>
    <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
      <t:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</t:TokenType>
      <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
      <t:Entropy>
        <t:BinarySecret u:Id="uuid-7195ad74-580b-4e52-9e2c-682e5a684345-1" Type="http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce">bI4xuyKwZ8OkQYBRnz2LDNV+zhIOnl0nwP24yI1QAwA=</t:BinarySecret>
      </t:Entropy>
      <t:KeySize>256</t:KeySize>
    </t:RequestSecurityToken>
  </s:Body>
</s:Envelope>

This seems to be very close however this appears to have actually encrypted the body of the soap message which is something I do NOT want to happen.

If I specify wsHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; with only using SecurityMode.Transport it goes back to where it says its anonymous.

What is the final hurdle I'm not able to clear on this?

Final Solution: Figured I'd post this incase it helps someone, there's not really much different here other the UserToken object is wrapped in a Security node which is what my service provider required and seems to be how its output from my previous examples from what I could get generated.

<system.serviceModel>
  <bindings>    
    <basicHttpBinding>
      <binding name="Contact" closeTimeout="00:01:00" openTimeout="00:01:00"
          receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
          bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="524288" maxBufferPoolSize="524288" maxReceivedMessageSize="524288"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
        <readerQuotas maxDepth="32" maxStringContentLength="65536" maxArrayLength="131072"
            maxBytesPerRead="32768" maxNameTableCharCount="131072" />
        <security mode="Transport">
          <transport clientCredentialType="None" proxyCredentialType="None"
              realm="" />
          <message clientCredentialType="UserName" algorithmSuite="Default" />
        </security>
      </binding>         
    </basicHttpBinding>
  </bindings>
  <client>
    <endpoint address="https://secure-ausomxana.crmondemand.com/Services/Integration"
       binding="basicHttpBinding" bindingConfiguration="Contact"
       contract="OnDemandContactService.Contact" name="OnDemand.Contact.Endpoint">
      <headers>        
        <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
          <wsse:UsernameToken>
            <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>
      </headers>
    </endpoint>
  </client>
</system.serviceModel>

See With C#, WCF SOAP consumer that uses WSSE plain text authentication? for how to configure it using code and not config

Community
  • 1
  • 1
Chris Marisic
  • 32,487
  • 24
  • 164
  • 258
  • Why do you set Basic transport security? Do you need both transport and message authentication? – Ladislav Mrnka May 02 '11 at 14:29
  • Do you think you could edit this to show a concise recipe? It's difficult to make it through the narrative; I'm not exactly clear on what you're adding and subtracting as the narrative continues. – Aren Cambre Jan 14 '13 at 22:24
  • @ArenCambre The concise version is if you expect a message to use `wsse:Security` you need to use static assignment in the web.config like i have in my final solution. If you need to vary the username and password, as far as I can tell you're SOL. – Chris Marisic Jan 15 '13 at 21:59
  • 1
    Thanks. I ended up posting my own question, and with the help of others, we landed on a way of doing it: http://stackoverflow.com/questions/14327960/with-c-wcf-soap-consumer-that-uses-wsse-plain-text-authentication/14334760 – Aren Cambre Jan 16 '13 at 01:31
  • the way you include the wsse security in configuration header is amazing and was trying to implement in the code which took me forever. – Praneeth Oct 09 '13 at 21:22
  • 1
    @Praneeth yes I gave up on trying to implement it in code, i never got it to succeed, but using this block it was smooth sailing for authenticating to the terrible soap api i had to deal with. – Chris Marisic Oct 10 '13 at 21:47
  • 2
    this question really helped me. thanx for asking :) – cahit beyaz May 19 '15 at 17:17
  • Were you username and password hardcoded into the config, or did you somehow update them at runtime? Great post though, very helpful. – DomBat Oct 06 '16 at 13:33
  • 1
    @DomBat i only ever used it straight in the config, the link at the bottom of my question might allow you to alter the credentials at run time. – Chris Marisic Oct 06 '16 at 18:22
  • 1
    Old post but we still have people using SOAP here... :( The client headers helped me fix my issue. I didn't need the extra transport and message settings in the binding. Thanks – Jim Jun 25 '18 at 23:13

3 Answers3

65

If you need to send UserName over HTTPS you can use standard approach (if your WSDL is correctly defined this should be created for you automatically by adding service reference):

<bindings>
  <basicHttpBinding>
    <binding name="secured">
      <security mode="TransportWithMessageCredential">
        <message clientCredentialType="UserName" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>
<client>
  <endpoint name="..." address="https://..." contract="..." binding="basicHttpBinding"
            bindingConfiguration="secured" />
</client>

Ar you can define binding in code:

var basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
basicHttpBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

You will set credentials in proxy as you do it now:

client.ClientCredentials.UserName.UserName = "bob";
client.ClientCredentials.UserName.Password = "1234";

If you only need UserNameToken profile over HTTP without any other WS-Security infrastructure the simplest approach is using ClearUserNameBinding.

If you need same user name and password for all requests from the client you can use simple basicHttpBinding without any security and include the static header from configuration:

<client>
  <endpoint ...>
    <headers>
      <wsse:UsernameToken xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' >
        <wsse:Username>Bob</wsse:Username>
        <wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'>
           1234
        </wsse:Password>
      </wsse:UsernameToken>
    </headers>
  </endpoint>
</client> 

If you need anything more complex show the relevant part of WSDL (security assertion) or sample SOAP request. Also mention if you are required to use HTTP or HTTPS.

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • 1
    Yes this header would be used in every request without any information changing, other than the requirement to send the UsernameToken the only other specification is that the service is accessed over HTTPS. – Chris Marisic May 02 '11 at 14:30
  • @Chris: I added some configuration for HTTPS – Ladislav Mrnka May 02 '11 at 14:40
  • Ladislav this definitely seems to be the solution, most specifically just shoving the header string into the endpoint definition. I appreciate your assistance as this type of configuration is not my strong suit. I'll follow up with the bounty award after speaking to the service provider it seems like I can communicate to them correctly now. Yet that their service is just busted now and not my soap messages. – Chris Marisic May 02 '11 at 15:30
  • 1
    This most definitely was the solution, I was never able to really get any where setting the username and password through code but using the headers element in configuration did it. – Chris Marisic May 03 '11 at 19:13
  • If you are getting a `MessageSecurityException`, look at my answer below. You might need to remove the timestamp and set the content-type as well. – Ionian316 Apr 30 '15 at 15:56
8

@Ladislav answer is correct. However, I was getting a MessageSecurityException for a SOAP 1.1 web service I was trying to use. Following this blog post from Scott Hanselman, I was able to make it work. This is the code I end up using:

    var oldBinding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
    oldBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
    //remove the timestamp
    BindingElementCollection elements = oldBinding.CreateBindingElements();
    elements.Find<SecurityBindingElement>().IncludeTimestamp = false;
    //sets the content type to application/soap+xml
    elements.Find<TextMessageEncodingBindingElement>().MessageVersion = MessageVersion.Soap11;
    CustomBinding newBinding = new CustomBinding(elements);
Ionian316
  • 2,303
  • 2
  • 28
  • 36
  • Reading through this blog post are you sure this actually answers my original question? Reading the post it appears that `MessageCredentialType.UserName` will send **only** a username, which would be useful for an ApiKey style auth while violating the WS-Security standards. I never understood why people use standards that instantly violate them, I guess it could be that the WS-* standards are so absurdly arcane no person can reasonably understand them to follow them even if they mean too. The WS-* standards set SOA back half a decade. "Microservices" are much closer to the purpose of SOA – Chris Marisic Apr 30 '15 at 16:20
  • Actually, the `ClientCredentialType` `UserName` includes both **UserName** and **Password** properties. @ladislav's answer helped me to get on the right path. I just needed to add a few more changes to make it work for the web service I'm calling. I posted this as an answer because 1) it may help others having the same issue, and 2) I know this should be a comment under his answer, but the code doesn't format well in a comment. I agree that the WS-* standards are overly complex. – Ionian316 May 01 '15 at 20:02
3

Use wsHttpBinding, not basicHttpBinding.

In fact, you should just use "Add Service Reference" and point to the WSDL of the service.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • 1
    I don't control the endpoint and the endpoint likely violates some series of the infinity permutations of the absurd WSSE "standards". – Chris Marisic May 19 '15 at 18:46
  • Wow, using WSHttpBinding instead of BasicHttpBinding sends a boatload of extra headers (WS-Addressing, Action, Trust etc.) in addition to Security. My request has doubled in size. No thanks to that. But "Add Service Reference" is a nice tip. – rustyx Aug 23 '18 at 10:22
  • @rustyx does your service use WSSE security? This question is about WSSE. – John Saunders Aug 23 '18 at 10:35
  • Yes, it uses WSSE password security (only). – rustyx Aug 23 '18 at 10:43
  • @rustyx then I don't see how that's working with basicHttpBinding. – John Saunders Aug 23 '18 at 10:45
  • 1
    Somehow setting `ClientCredentialType = BasicHttpMessageCredentialType.UserName` sends the proper WS-Security header. – rustyx Aug 23 '18 at 10:54