Correct way communicate WSSE Usernametoken for SOAP webservice
Asked Answered
G

3

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

Griffey answered 29/4, 2011 at 19:47 Comment(10)
Why do you set Basic transport security? Do you need both transport and message authentication?Chisel
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.Kent
@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.Griffey
Thanks. I ended up posting my own question, and with the help of others, we landed on a way of doing it: #14328460Kent
the way you include the wsse security in configuration header is amazing and was trying to implement in the code which took me forever.Durkheim
@Durkheim 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.Griffey
this question really helped me. thanx for asking :)Scutter
Were you username and password hardcoded into the config, or did you somehow update them at runtime? Great post though, very helpful.Hornbill
@Hornbill 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.Griffey
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. ThanksBlastoff
C
66

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.

Chisel answered 2/5, 2011 at 14:18 Comment(5)
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.Griffey
@Chris: I added some configuration for HTTPSChisel
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.Griffey
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.Griffey
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.Orometer
O
9

@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);
Orometer answered 30/4, 2015 at 15:53 Comment(2)
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 SOAGriffey
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.Orometer
S
3

Use wsHttpBinding, not basicHttpBinding.

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

Selfregulated answered 29/4, 2011 at 19:54 Comment(6)
I don't control the endpoint and the endpoint likely violates some series of the infinity permutations of the absurd WSSE "standards".Griffey
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.Demythologize
@Demythologize does your service use WSSE security? This question is about WSSE.Selfregulated
Yes, it uses WSSE password security (only).Demythologize
@Demythologize then I don't see how that's working with basicHttpBinding.Selfregulated
Somehow setting ClientCredentialType = BasicHttpMessageCredentialType.UserName sends the proper WS-Security header.Demythologize

© 2022 - 2024 — McMap. All rights reserved.