Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

WCF WS-Security and WSE Nonce Authentication


:P
On this page:

WCF makes it fairly easy to access WS-* Web Services, except when you run into a service format that it doesn't support. Even then WCF provides a huge amount of flexibility to make the service clients work, however finding the proper interfaces to make that happen is not easy to discover and for the most part undocumented unless you're lucky enough to run into a blog, forum or StackOverflow post on the matter.

This is definitely true for the Password Nonce as part of the WS-Security/WSE protocol, which is not natively supported in WCF. Specifically I had a need to create a WCF message on the client that includes a WS-Security header that looks like this from their spec document:

<soapenv:Header>
  <wsse:Security soapenv:mustUnderstand="1"
      xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <wsse:UsernameToken wsu:Id="UsernameToken-8"
      xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <wsse:Username>TeStUsErNaMe1</wsse:Username>
      <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"                  >TeStPaSsWoRd1</wsse:Password>
      <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"                  >f8nUe3YupTU5ISdCy3X9Gg==</wsse:Nonce>
      <wsu:Created>2011-05-04T19:01:40.981Z</wsu:Created>
    </wsse:UsernameToken>
  </wsse:Security>
</soapenv:Header>

Specifically, the Nonce and Created keys are what WCF doesn't create or have a built in formatting for.

Why is there a nonce? My first thought here was WTF? The username and password are there in clear text, what does the Nonce accomplish? The Nonce and created keys are are part of WSE Security specification and are meant to allow the server to detect and prevent replay attacks. The hashed nonce should be unique per request which the server can store and check for before running another request thus ensuring that a request is not replayed with exactly the same values.

Basic ServiceUtl Import - not much Luck

The first thing I did when I imported this service with a service reference was to simply import it as a Service Reference. The Add Service Reference import automatically detects that WS-Security is required and appropariately adds the WS-Security to the basicHttpBinding in the config file:

If if I run this as is using code like this:

var client = new RealTimeOnlineClient();

client.ClientCredentials.UserName.UserName = "TheUsername";
client.ClientCredentials.UserName.Password = "ThePassword";

I get nothing in terms of WS-Security headers. The request is sent, but the the binding expects transport level security to be applied, rather than message level security. To fix this so that a WS-Security message header is sent the security mode can be changed to:

<security mode="TransportWithMessageCredential" />

Now if I re-run I at least get a WS-Security header which looks like this:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <o:Security s:mustUnderstand="1"
                xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <u:Timestamp u:Id="_0">
        <u:Created>2012-11-24T02:55:18.011Z</u:Created>
        <u:Expires>2012-11-24T03:00:18.011Z</u:Expires>
      </u:Timestamp>
      <o:UsernameToken u:Id="uuid-18c215d4-1106-40a5-8dd1-c81fdddf19d3-1">
        <o:Username>TheUserName</o:Username>
        <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"
                   >ThePassword</o:Password>
      </o:UsernameToken>
    </o:Security>
  </s:Header>

Closer! Now the WS-Security header is there along with a timestamp field (which might not be accepted by some WS-Security expecting services), but there's no Nonce or created timestamp as required by my original service.

Using a CustomBinding instead

My next try was to go with a CustomBinding instead of basicHttpBinding as it allows a bit more control over the protocol and transport configurations for the binding. Specifically I can explicitly specify the message protocol(s) used. Using configuration file settings here's what the config file looks like:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="CustomSoapBinding">
          <security includeTimestamp="false"
                    authenticationMode="UserNameOverTransport"
                    defaultAlgorithmSuite="Basic256"
                    requireDerivedKeys="false"
                    messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
          </security>
          <textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
          <httpsTransport maxReceivedMessageSize="2000000000"/>
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="https://notrealurl.com:443/services/RealTimeOnline"
                binding="customBinding"
                bindingConfiguration="CustomSoapBinding"
                contract="RealTimeOnline.RealTimeOnline"
                name="RealTimeOnline" />
    </client>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0"
                      sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

This ends up creating a cleaner header that's missing the timestamp field which can cause some services problems. The WS-Security header output generated with the above looks like this:

<s:Header>
  <o:Security s:mustUnderstand="1"
              xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <o:UsernameToken u:Id="uuid-291622ca-4c11-460f-9886-ac1c78813b24-1">
      <o:Username>TheUsername</o:Username>
      <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"                 >ThePassword</o:Password>
    </o:UsernameToken>
  </o:Security>
</s:Header>

This is closer as it includes only the username and password.

The key here is the protocol for WS-Security:

messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"

Quite the mouthful, eh?

This explicitly specifies the protocol version. There are several variants of this specification but none of them seem to support the nonce unfortunately. This protocol does allow for optional omission of the Nonce and created timestamp provided (which effectively makes those keys optional). With some services I tried that requested a Nonce just using this protocol actually worked where the default basicHttpBinding failed to connect, so this is a possible solution for access to some services.

Unfortunately for my target service that was not an option. The nonce has to be there.

Creating Custom ClientCredentials

As it turns out WCF doesn't have support for the Digest Nonce as part of WS-Security, and so as far as I can tell there's no way to do it just with configuration settings. I did a bunch of research on this trying to find workarounds for this, and I did find a couple of entries on StackOverflow as well as on the MSDN forums. However, none of these are particularily clear and I ended up using bits and pieces of several of them to arrive at a working solution in the end.

The latter forum message is the more useful of the two (the last message on the thread in particular) and it has most of the information required to make this work. But it took some experimentation for me to get this right so I'll recount the process here maybe a bit more comprehensively.

In order for this to work a number of classes have to be overridden:

  • ClientCredentials
  • ClientCredentialsSecurityTokenManager
  • WSSecurityTokenizer

The idea is that we need to create a custom ClientCredential class to hold the custom properties so they can be set from the UI or via configuration settings. The TokenManager and Tokenizer are mainly required to allow the custom credentials class to flow through the WCF pipeline and eventually provide custom serialization.

Here are the three classes required and their full implementations:

public class CustomCredentials : ClientCredentials
{
    public CustomCredentials()
    { }

    protected CustomCredentials(CustomCredentials cc)
        : base(cc)
    { }

    public override System.IdentityModel.Selectors.SecurityTokenManager CreateSecurityTokenManager()
    {
        return new CustomSecurityTokenManager(this);
    }

    protected override ClientCredentials CloneCore()
    {
        return new CustomCredentials(this);
    }
}

public class CustomSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
    public CustomSecurityTokenManager(CustomCredentials cred)
        : base(cred)
    { }

    public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
    {
        return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity11);
    }
}

public class CustomTokenSerializer : WSSecurityTokenSerializer
{
    public CustomTokenSerializer(SecurityVersion sv)
        : base(sv)
    { }

    protected override void WriteTokenCore(System.Xml.XmlWriter writer,
                                            System.IdentityModel.Tokens.SecurityToken token)
    {
        UserNameSecurityToken userToken = token as UserNameSecurityToken;

        string tokennamespace = "o";

        DateTime created = DateTime.Now;
        string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");

        // unique Nonce value - encode with SHA-1 for 'randomness'
        // in theory the nonce could just be the GUID by itself
        string phrase = Guid.NewGuid().ToString();
        var nonce = GetSHA1String(phrase);

        // in this case password is plain text
        // for digest mode password needs to be encoded as:
        // PasswordAsDigest = Base64(SHA-1(Nonce + Created + Password))
        // and profile needs to change to
        //string password = GetSHA1String(nonce + createdStr + userToken.Password);

        string password = userToken.Password;

        writer.WriteRaw(string.Format(
        "<{0}:UsernameToken u:Id=\"" + token.Id +
        "\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
        "<{0}:Username>" + userToken.UserName + "</{0}:Username>" +
        "<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
        password + "</{0}:Password>" +
        "<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
        nonce + "</{0}:Nonce>" +
        "<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
    }

    protected string GetSHA1String(string phrase)
    {
        SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
        byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
        return Convert.ToBase64String(hashedDataBytes);
    }

}

Realistically only the CustomTokenSerializer has any significant code in. The code there deals with actually serializing the custom credentials using low level XML semantics by writing output into an XML writer.

I can't take credit for this code - most of the code comes from the MSDN forum post mentioned earlier - I made a few adjustments to simplify the nonce generation and also added some notes to allow for PasswordDigest generation.

Per spec the nonce is nothing more than a unique value that's supposed to be 'random'. I'm thinking that this value can be any string that's unique and a GUID on its own probably would have sufficed. Comments on other posts that GUIDs can be potentially guessed are highly exaggerated to say the least IMHO. To satisfy even that aspect though I added the SHA1 encryption and binary decoding to give a more random value that would be impossible to 'guess'. The original example from the forum post used another level of encoding and decoding to string in between - but that really didn't accomplish anything but extra overhead.

The header output generated from this looks like this:

<s:Header>
  <o:Security s:mustUnderstand="1"
              xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <o:UsernameToken u:Id="uuid-f43d8b0d-0ebb-482e-998d-f544401a3c91-1"
                      xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <o:Username>TheUsername</o:Username>
      <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">ThePassword</o:Password>
      <o:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
                     >PjVE24TC6HtdAnsf3U9c5WMsECY=</o:Nonce>
      <u:Created>2012-11-23T07:10:04.670Z</u:Created>
    </o:UsernameToken>
  </o:Security>
</s:Header>

which is exactly as it should be.

Password Digest?

In my case the password is passed in plain text over an SSL connection, so there's no digest required so I was done with the code above.

Since I don't have a service handy that requires a password digest,  I had no way of testing the code for the digest implementation, but here is how this is likely to work. If you need to pass a digest encoded password things are a little bit trickier. The password type namespace needs to change to:

http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#Digest

and then the password value needs to be encoded. The format for password digest encoding is this:

Base64(SHA-1(Nonce + Created + Password))

and it can be handled in the code above with this code (that's commented in the snippet above):

string password = GetSHA1String(nonce + createdStr + userToken.Password);

The entire WriteTokenCore method for digest code looks like this:

protected override void WriteTokenCore(System.Xml.XmlWriter writer,
                                        System.IdentityModel.Tokens.SecurityToken token)
{
    UserNameSecurityToken userToken = token as UserNameSecurityToken;

    string tokennamespace = "o";

    DateTime created = DateTime.Now;
    string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");

    // unique Nonce value - encode with SHA-1 for 'randomness'
    // in theory the nonce could just be the GUID by itself
    string phrase = Guid.NewGuid().ToString();
    var nonce = GetSHA1String(phrase);

    string password = GetSHA1String(nonce + createdStr + userToken.Password);

    writer.WriteRaw(string.Format(
    "<{0}:UsernameToken u:Id=\"" + token.Id +
    "\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
    "<{0}:Username>" + userToken.UserName + "</{0}:Username>" +
    "<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#Digest\">" +
    password + "</{0}:Password>" +
    "<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
    nonce + "</{0}:Nonce>" +
    "<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));        
}

I had no service to connect to to try out Digest auth - if you end up needing it and get it to work please drop a comment…

How to use the custom Credentials

The easiest way to use the custom credentials is to create the client in code.

Here's a factory method I use to create an instance of my service client: 

public static RealTimeOnlineClient CreateRealTimeOnlineProxy(string url,
                                                                string username,
                                                                string password)
{
    if (string.IsNullOrEmpty(url))
        url = "https://notrealurl.com:443/cows/services/RealTimeOnline";
            
    CustomBinding binding = new CustomBinding();  

    var security = TransportSecurityBindingElement.CreateUserNameOverTransportBindingElement();
    security.IncludeTimestamp = false;
    security.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
    security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;

    var encoding = new TextMessageEncodingBindingElement();
    encoding.MessageVersion = MessageVersion.Soap11;

    var transport = new HttpsTransportBindingElement();
    transport.MaxReceivedMessageSize = 20000000; // 20 megs

    binding.Elements.Add(security);
    binding.Elements.Add(encoding);
    binding.Elements.Add(transport);

    RealTimeOnlineClient client = new RealTimeOnlineClient(binding,
        new EndpointAddress(url));

    // to use full client credential with Nonce uncomment this code:
    // it looks like this might not be required - the service seems to work without it
    client.ChannelFactory.Endpoint.Behaviors.Remove<System.ServiceModel.Description.ClientCredentials>();
    client.ChannelFactory.Endpoint.Behaviors.Add(new CustomCredentials());

    client.ClientCredentials.UserName.UserName = username;
    client.ClientCredentials.UserName.Password = password;

    return client;
}

This returns a service client that's ready to call other service methods.

The key item in this code is the ChannelFactory endpoint behavior modification that that first removes the original ClientCredentials and then adds the new one. The ClientCredentials property on the client is read only and this is the way it has to be added.

Summary

It's a bummer that WCF doesn't suport WSE Security authentication with nonce values out of the box. From reading the comments in posts/articles while I was trying to find a solution, I found that this feature was omitted by design as this protocol is considered unsecure. While I agree that plain text passwords are rarely a good idea even if they go over secured SSL connection as WSE Security does, there are unfortunately quite a few services (mosly Java services I suspect) that use this protocol. I've run into this twice now and trying to find a solution online I can see that this is not an isolated problem - many others seem to have struggled with this. It seems there are about a dozen questions about this on StackOverflow all with varying incomplete answers. Hopefully this post provides a little more coherent content in one place.

Again I marvel at WCF and its breadth of support for protocol features it has in a single tool. And even when it can't handle something there are ways to get it working via extensibility. But at the same time I marvel at how freaking difficult it is to arrive at these solutions. I mean there's no way I could have ever figured this out on my own. It takes somebody working on the WCF team or at least being very, very intricately involved in the innards of WCF to figure out the interconnection of the various objects to do this from scratch. Luckily this is an older problem that has been discussed extensively online and I was able to cobble together a solution from the online content. I'm glad it worked out that way, but it feels dirty and incomplete in that there's a whole learning path that was omitted to get here…

Man am I glad I'm not dealing with SOAP services much anymore. REST service security - even when using some sort of federation is a piece of cake by comparison 😃 I'm sure once standards bodies gets involved we'll be right back in security standard hell…

Posted in WCF  Web Services  

The Voices of Reason


 

Yaron Naveh
November 24, 2012

# re: WCF WS-Security and WSE Nonce Authentication

In these kinds of situations I like to emit the security header myself using a message inspector (or a custom encoder if required) rather than hook up to the security pipeline. In some cases this can save you from implementing 10+ classes.

For your scenario you may also want to look at this implementation of the username token profile for wcf http://blogs.msdn.com/b/aszego/archive/2010/06/24/usernametoken-profile-vs-wcf.aspx

Donatas Mačiūnas
December 19, 2012

# re: WCF WS-Security and WSE Nonce Authentication

Thank you very much for this! We had this same issue with Java webservice and your solution worked perfectly.

Just something I noticed with fiddler:
by default WCF outputs password header like this:
"<{0}:Password {0}:Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#Digest\">"
note: {0}:Type, your solution is missing {0}: before Type attribute (it still works though)

Alexander Shiryaev
March 01, 2013

# re: WCF WS-Security and WSE Nonce Authentication

Created date should be in UTC:
DateTime created = DateTime.UtcNow;
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

Balaji
April 10, 2013

# re: WCF WS-Security and WSE Nonce Authentication

Thank you! i understand how painful it was to figure this.

Charlie
May 04, 2013

# re: WCF WS-Security and WSE Nonce Authentication

Hi, After spending nights and days on this for a beginner like me,,, I came to the point where I generated a Clientproxyclas using svcutil.exe. And then came to the understanding that I would need some custom bindings to accomodate nonce.. But How do I get to see the Soap XML. The SVClog file just shows some applicationdata and events..

Steve
July 29, 2013

# re: WCF WS-Security and WSE Nonce Authentication

This post was super helpful! I had visited probably about 10 different pages and none outlined the solution so thoroughly. I am now getting my header constructed properly but am still getting an error back from the service "com.sun.xml.wss.impl.WssSoapFaultException: Authentication of Username Password Token Failed". I'm following up with the service host since I believe I'm following the proper steps now.

Rick Strahl
July 30, 2013

# re: WCF WS-Security and WSE Nonce Authentication

@Steve - the easiest way to check is to have the provider give you a service trace with credentials and then let you recreate that trace with the same credentials. That way you can double check the encoding. I've found that in some cases the encoding of the nonce can be different and double checking whether they match can be useful to experimenting and finding the exact right combo.

Steve
July 30, 2013

# re: WCF WS-Security and WSE Nonce Authentication

Thanks @Rick, it was a huge pain figuring out a workable request with the client but it ended up working for me using this approach:

string rawNonce = Guid.NewGuid().ToString();
var nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(rawNonce));
string password = GetSHA1String(rawNonce + createdStr + userToken.Password);

I was hashing the nonce string and then rehashing the nonce string again as part of the overall password hash.

Either way, I was totally lost before your post so you still really helped a ton! Thanks!

kbusch
October 28, 2013

# re: WCF WS-Security and WSE Nonce Authentication

After an all day search, this post saved me today. Thank you for taking the time to write this up and share. You make the internet work!

tmanthey
November 08, 2013

# re: WCF WS-Security and WSE Nonce Authentication

You can actually see the message that goes over the wire in svcTraceViewer.

You just have to set logMessagesAtTransportLevel="true"

so <messageLogging logEntireMessage="true" logMalformedMessages="true"
logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="true"

Dave Lee
November 12, 2013

# re: WCF WS-Security and WSE Nonce Authentication

Thank you so much for sharing this. I had to make a couple of minor tweaks because the service that I am calling is over HTTP and not HTTPS.

 CustomBinding binding = new CustomBinding();
 
var security = TransportSecurityBindingElement.CreateUserNameOverTransportBindingElement();
security.AllowInsecureTransport = true; // [DLee 11-12-2013] Added
security.IncludeTimestamp = false;
security.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
security.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
 
var encoding = new TextMessageEncodingBindingElement();
encoding.MessageVersion = MessageVersion.Soap11;
 
var transport = new HttpTransportBindingElement(); // [DLee 11-12-2013] new HttpsTransportBindingElement();
transport.MaxReceivedMessageSize = 20000000; // 20 megs

Sabari
February 11, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Can you please tell me how we can achieve this using .net 2.0 framework.

Mohankumar
February 25, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Very helpful. thanks a lot.

I would like to know how we can expose the WCF service with OASIS security ?
can you help on this as well ?

Tom
April 30, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Super Tutorial, Solved our Problem in 5min after trying 4 days!!!!!!

Carlos Garcia
June 13, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Hi, i have read the article and i´m triyng to implement it. But i cannot find the Namespace for the RealTimeOnlineClient class. Can somebody tell me where to find it?

Rick Strahl
June 13, 2014

# re: WCF WS-Security and WSE Nonce Authentication

@Carlos - that's an application specific class. That class is going to be your WCF client class.

Dennis
June 23, 2014

# re: WCF WS-Security and WSE Nonce Authentication

@Steve - the simple rawnonce is what made the difference for me. This page helped confirm I was on the right track. You comment made it work. I have been only trying for 10 hours so far to get this all to work. The last 5 was this one issue. I was trying in a language I never used before (apex, salesforce) and thanks to your comment was able to do so.

Scott Eastin
July 06, 2014

# re: WCF WS-Security and WSE Nonce Authentication

I just wanted to say this article was a giant help. There is barely anything out there about how to solve these types of problems and this article was the solution to the developer hell I have been in for the last week. Thanks again and I owe you several beers!

Scott

John Herron
July 06, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Hi Rick - I'm getting an h:Security section before the o:Security section. The web service I'm calling fails because of this with a "user could not be authenticated" error. If I capture the message with fiddler then remove the section or change the h:Security spelling it works. Did you remove the section from your post or does it include it? I need a way to remove the h:Security section.

Thanks
John

Moises
August 27, 2014

# re: WCF WS-Security and WSE Nonce Authentication

Oh! you saved my life with this post. It explains it so clearly and all the steps together. Thank you very much!

nikz2
October 23, 2014

# re: WCF WS-Security and WSE Nonce Authentication

FOR PASSWORD DIGEST! It works, but need some changes,
1st
"<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#Digest\">" +
change to
"<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" +

2nd as described here
http://stackoverflow.com/questions/19438000/working-algorithm-for-passworddigest-in-ws-security

Praveen Behara
January 30, 2015

# re: WCF WS-Security and WSE Nonce Authentication

Hi,
This is an excellent post and bang on the issue we were facing. However, we didn't want the binding to be created in the code.. and hence we removed the code and put in the configuration. So, only the part where the behavior element is replaced is left out for us.

Also, I have read the comments by others and thinking in the direction of custom behaviors / message inspectors. Since, our implementation is in BizTalk (leveraging WCF capabilities), we guess we might incline towards the message inspectors route.

Thanks again.
PKB.

Francesco
February 06, 2015

# re: WCF WS-Security and WSE Nonce Authentication

hi
first og thank you so much for your post it's very helpfull.
i have some problem with your solution in post message. the writer.writerow dosen't append the userNameToken tag but if i add <o:Security><o:UsernameToken>...</0:UsernameToken></Security>
the writer.writeRow works properly but the message contains two tags security like this:
<o:Security><o:Security><o:UsernameToken>...</0:UsernameToken></Security></Security>

do you how can i resolve the problem? i'm using vb.net instead of c#
thank you so much
Francesco

Doug S
July 22, 2015

# re: WCF WS-Security and WSE Nonce Authentication

Many thanks. I'm consuming a Java service that was failing due to the missing EncodingType attribute on nonce. And, bonus- you let me cut the WSE cord.

Andrés
September 07, 2015

# re: WCF WS-Security and WSE Nonce Authentication

I need that the binding
work with the http protocol. I change de binding but I see this error "The provided URI scheme 'http' is invalid; expected 'https'."

My binding is This:
<binding name="EmisionTarjetasImplPortSoapBinding" >
<security allowInsecureTransport ="true" enableUnsecuredResponse ="true"
includeTimestamp="false"
defaultAlgorithmSuite="Basic256"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport maxReceivedMessageSize="2000000000"/>
</binding>

Tim B.
January 15, 2016

# re: WCF WS-Security and WSE Nonce Authentication

@Andres: is it because of the httpsTransport attribute being there (note the httpS part)?

David
February 17, 2016

# re: WCF WS-Security and WSE Nonce Authentication

I got the security to work, but there's a slight bug in the ToString of the created date. It should be:

created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

(Note: The HH in capitals rather than hh, to send through the hour component based on a 24-hour clock).

It may work for you without making this change, but depending on how close to the time the server requires the request to be, you may find the solution only works in the morning...

For the server I connect to, I actually needed to change it to:

created.ToString("yyyy-MM-ddTHH:mm:ssZ");

Ram
February 25, 2016

# re: WCF WS-Security and WSE Nonce Authentication

Hi,

Great post :) not able to find any article with this information.

We got same requirement. I am trying to implement this. I struck with the following error.

The 'CustomBinding'.'http://tempuri.org/' binding for the 'webservicename'.'our web service url' contract is configured with an authentication mode that requires transport level integrity and confidentiality. However the transport cannot provide integrity and confidentiality.

On debugging, I can see that its not calling createsecuritytokenmanager() or tokenserializer() functions in these custom classes. Do i need to do any thing additional to call these methods?

Mark
March 11, 2016

# re: WCF WS-Security and WSE Nonce Authentication

Thanks, exactly what I needed.

I used a standard date and time format string, as follows, to generate the same result:

    string created = DateTime.UtcNow.ToString("O");

MGorgon
September 01, 2016

# re: WCF WS-Security and WSE Nonce Authentication

Your PasswordDigest is calculated wrong.
Here's the valid code:

public static string GetPasswordDigest(string nonce, string created, string pass)
{
byte[] bNonce = Convert.FromBase64String(nonce);
byte[] bCreated = Encoding.UTF8.GetBytes(created);
byte[] bPass = Encoding.UTF8.GetBytes(pass);
byte[] bAll = new byte[bNonce.Length + bCreated.Length + bPass.Length];

Buffer.BlockCopy(bNonce, 0, bAll, 0, bNonce.Length);
Buffer.BlockCopy(bCreated, 0, bAll, bNonce.Length, bCreated.Length);
Buffer.BlockCopy(bPass, 0, bAll, bNonce.Length + bCreated.Length, bPass.Length);

return Sha1Base64Digest(bAll);
}

public static String Sha1Base64Digest(byte[] phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(phrase);
return Convert.ToBase64String(hashedDataBytes);
}

Rick Strahl
September 01, 2016

# re: WCF WS-Security and WSE Nonce Authentication

@MGorgon - uhm no. It depends on the rules for the Digest creation which can vary - different protocols, potentially different organization of the layout.

Emilio
September 26, 2016

# re: WCF WS-Security and WSE Nonce Authentication

In the method WriteTokenCore, created datetime should be :

DateTime created = DateTime.UtcNow;

The time for the server and client must be synchronized, so should and would be send in UTC

Kinfd regards

Andreas
November 24, 2016

# re: WCF WS-Security and WSE Nonce Authentication

i have a Problem with call the EndpoitBehavior stored in a dll and called from Windows Service. The CustomCredentials() call is not executed and the new configuration is empty. Same Source called from Application Project work fine. Any Ideas?

Here the Error Message:

The 'CustomBinding'.'http://tempuri.org/' binding for the 'PersonServicePortType'.'http://www.his.de/ws/PersonService' contract is configured with an authentication mode that requires transport level integrity and confidentiality. However the transport cannot provide integrity and confidentiality.


Kenan
January 14, 2017

# re: WCF WS-Security and WSE Nonce Authentication

Works like a charm. Thanks.


Ramesh
February 22, 2017

# re: WCF WS-Security and WSE Nonce Authentication

Hi Rick, thanks for providing this detailed solution as I am running into a similar issue where my WCF client need to send nonce to Java WS. However, this solution doesn't work for .NET 4.5.x because 'IdentityModel' does not exist in the namespace 'System'. Any suggestions on how to make it work? Thanks in advance.


Rick Strahl
February 22, 2017

# re: WCF WS-Security and WSE Nonce Authentication

@Remesh - You need to add a reference to System.IdentityModel...


Ramesh
February 23, 2017

# re: WCF WS-Security and WSE Nonce Authentication

Hi Rick, thanks again. I don't see control coming to CustomCredentials or CustomTokenSerializer classes and I don't see Nonce and Created being added to header. No errors as well. Am I missing something?


Phantom2017
October 06, 2017

# re: WCF WS-Security and WSE Nonce Authentication

Someone has asked a question on how to inspect the actual SOAP messages; the thing is even if you enable logging from diagnostics (& set all attributes to true), in most cases you will not see the Security headers (when you are using client certificates). A good alternative is the "Charles" tool ( https://www.charlesproxy.com/ ) - it is just brilliant / very easy to setup. You would have to select the client certificate within Charles and after that it simply acts as your proxy & will show you all outgoing & incoming messages.

PS: Thank you Rick for documenting this - as with several others, I have also spent a lot of hours on Stack overflow to get the hang of things. It'd have been great if I had found your article earlier. I found the "Charles" tool reference in one of StackOverflow articles & it has been a huge help - without being to see the actual messages, it is big black box & can be extremely frustrating.


Robt. P.
May 02, 2018

# re: WCF WS-Security and WSE Nonce Authentication

Thanks very much, Mr. Strahl! This was just the right amount of information (and a bit more - we didn't need the digest for the particular web service we're talking to) and better summarized than any of the related spots. Search engines did you (and the rest of us) right by putting this page in the first few hits.

@tmanthey - even with logMessage set to true, svcTraceViewer elides the attributes from the elements within a UserNameToken (at least the version I'm using does). I found WcfSoapLogger to be helpful in this regard.

Note that the WCF sources are available online (but these may be of limited help. And, as you noted, you ain't gonna get a nonce out of Microsoft's extensive Ws-* code. They state this explicitly.)

Finally, I think it's somewhat more maintainable to write the XML with something like XLinq (more maintainable but less efficient, which doesn't really matter for the bandwith we're dealing with). On the chance that I'm not the only one who cares, I'm including the relevant snippet:

            var ws2004Prefix = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-";
            var u = (XNamespace)(ws2004Prefix + "wssecurity-utility-1.0.xsd");
            var o = (XNamespace)(ws2004Prefix + "wssecurity-secext-1.0.xsd");
            var pwdTextType =   (ws2004Prefix + "username-token-profile-1.0#PasswordText");
            var base64Type  =   (ws2004Prefix + "soap-message-security-1.0#Base64Binary");
            var xDoc = new XDocument(
                new XElement("root",
                    new XAttribute(XNamespace.Xmlns + "o", o),
                    new XElement(o + "UsernameToken",
                        new XAttribute(u + "Id", token.Id),
                        new XAttribute(XNamespace.Xmlns + "u", u),
                        new XElement(o + "Username", userToken.UserName),
                        new XElement(o + "Password", new XAttribute("Type", pwdTextType), userToken.Password),
                        new XElement(o + "Nonce",    new XAttribute("EncodingType", base64Type), nonce) /*,
                        new XElement(u + "Created",  DateTime.UtcNow)*/ )));

            xDoc.Root.Element(o + "UsernameToken").WriteTo(writer);

Damian Koscielniak
August 02, 2018

# re: WCF WS-Security and WSE Nonce Authentication

My Soap message should look like this: <o:Username></o:Username> <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"></o:Password> <o:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"></o:Nonce> <u:Created>2018-08-02T10:26:53.832Z</u:Created>

But it's not... Parameters are not added to Password and Nonce: <o:Username></o:Username> <o:Password></o:Password> <o:Nonce></o:Nonce> <u:Created>2018-08-02T10:54:41.247Z</u:Created>

But if I add a test field new XElement(o + "TestField", new XAttribute("Test", pwdTextType), "testField") Then code looks like this:<o:TestField Test="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">testField</o:TestField>

Then why attributes are not being added to Password and Nonce?


Tolga
September 26, 2018

# re: WCF WS-Security and WSE Nonce Authentication

Is it possible to make nonce authentication in .Net Standards 2.0?

I could not manage to use the CustomCredentials in my code.

Thanks for your feedback in advance


arnc
October 22, 2018

# re: WCF WS-Security and WSE Nonce Authentication

Very good post. In my project, I need to add both BinarySecurityToken and UsernameToken together and I applied this solution, .NET doesn't allow me to add UsernameToken part. Is there any way for that?

Thanks in advance.


Gilson
October 26, 2018

# re: WCF WS-Security and WSE Nonce Authentication

Hi Rick, good afternoon.

Thanks for this article.

I'm following it, but I'm getting the error: System.ServiceModel.FaultException: 'The message has expired'

Already tried add:

binding.OpenTimeout = new TimeSpan (0, 10, 0);

binding.CloseTimeout = new TimeSpan (0, 10, 0);

binding.SendTimeout = new TimeSpan (0, 10, 0);

binding.ReceiveTimeout = new TimeSpan (0, 10, 0);

But I still get that error.


anonymous
February 27, 2020

# re: WCF WS-Security and WSE Nonce Authentication

Hey,

Thanks this instructive article.I have applied this steps and i see the security section but messageId,to,action parameters is gone now. I think, it's related the "MessageSecurityVersion" on the binding. How can i create action,to,messageId parameters with security node in the header?


Anthony Miller
June 04, 2020

# re: WCF WS-Security and WSE Nonce Authentication

Thanks. This was a lot of help.

For password digest, I had to make some changes.

  1. (I think nikz2 mention this above) "<{0}:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#Digest">" + change to "<{0}:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">" +

  2. I had to use created.ToString("yyyy-MM-ddTHH:mm:ssZ") or I would get message expired. Same as gilson above.

  3. Instead of string password = GetSHA1String(nonce + createdStr + userToken.Password); I used password = CreateHashedPassword(nonce, createdStr, userToken.Password);

private string CreateHashedPassword(string nonceStr, string created, string password)

   {

            var nonce = Convert.FromBase64String(nonceStr); 

            var createdBytes = Encoding.UTF8.GetBytes(created);

            var passwordBytes = Encoding.UTF8.GetBytes(password);

            var combined = new byte[createdBytes.Length + nonce.Length + passwordBytes.Length];

            Buffer.BlockCopy(nonce, 0, combined, 0, nonce.Length);

            Buffer.BlockCopy(createdBytes, 0, combined, nonce.Length, createdBytes.Length);

            Buffer.BlockCopy(passwordBytes, 0, combined, nonce.Length + createdBytes.Length, passwordBytes.Length);

            return Convert.ToBase64String(SHA1.Create().ComputeHash(combined));

        }

Hope this helps someone else....


Ali
April 22, 2021

# re: WCF WS-Security and WSE Nonce Authentication

Anthony Miller, man you saved me. I was banging my head against wall on how to solve these issues and found nothing for password digest. I was having same error as gilson or token cannot be authorized. Just build the XML, replaced the values and it worked like a charm.


Rich
October 27, 2022

# re: WCF WS-Security and WSE Nonce Authentication


Valentin
February 21, 2023

# re: WCF WS-Security and WSE Nonce Authentication

I normally don't leave comments, but man, thanks so much for this article. It saved me a lot of time and headaches, you are a legend!


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024