Using WCF in BizTalk with web services requiring NTLM authentication
Posted on October 22nd, 2010 by Fredrik in Debugging, ProgrammingDuring the past day I’ve been struggling with a rather peculiar problem. It involves the following ingredients:
- BizTalk Server 2006 R2
- WCF (Windows Communication Foundation)
- A web service running on an IIS platform requiring NTLM as the authentication method
- No authentication requirement for downloading the service metadata (WSDL)
I’ll spill the beans right away – you won’t succeed. At least not without building something involving unmanaged code (see this article for further information). This here, rather lengthy, article aims at explaining the reasons why you won’t succeed – it won’t give you a solution in the end, sorry about that. The only solution which might work is the one previously linked, haven’t tried it though. In the end we ended up opting for another authentication mechanism.
Here’s the whole story:
Due to the fact that generating the WCF client required no authentication the following problem didn’t surface until rather late in our development process, actually as late as when we were doing final tests in the BizTalk test environment.
So, what happens is this: the WCF port in BizTalk tries to send a SOAP message to the web service it’s configured to connect to. The web service states that the client (the BizTalk server in this case) is unathorized (HTTP 401, to be specific). Along with this message it says that the allowed authentication method is NTLM. As a result of the fact that downloading the service metadata required no authentication the generated configuration for the WCF port in BizTalk states nothing about how it should proceed to actually authenticate. What to do?
By now you’ll be digging around the various dialogs in the WCF adapter configuration in BizTalk looking for a place to enter a user name and a password. Depending on the port type chosen (WCF-Custom, WCF-BasicHttp or WCF-WSHttp) and the chosen binding (wsHttpBinding, basicHttpBinding or customBinding) the dialogs might look a little different. How ever, the end result is this:
- For any other port type than WCF-Custom there’ll be a tab called Security allowing you to enter credentials – but only if the authentication method chosen is set to “Basic” (or “Digest”, but that’s not really interesting for this scenario). For “NTLM” the Edit button allowing you to configure the credentials will be disabled.
- If you’ve chosen WCF-Custom as your port type there’ll be a tab named Credentials where you can enter a user name and password. Only problem is that this information will be ignored if you set the clientCredentialType to “NTLM” within the binding configuration for your endpoint. So no luck there either.
- Not even if you add the clientCredentials extension as a behavior and configure that behavior to be used for your endpoint you get settings for user name and password. What you do get though is a truck load of options for configuring client certificate authentication. Yay.
Just for fun one could try to set the authentication method (clientCredentialType) to “NTLM” within the binding, and never mind the fact that won’t be able to enter a desired user name and password. This only gets interesting with web proxy that’ll show you what is exchange between your client and the server. An excellent program to use for this is Fiddler. What you’ll notice after running the test is that the WCF port will try to authenticate with the service using the credentials held by the host process (the BizTalk host instance in this case). Of course the server hosting the web service won’t know anything about that account and therefore it’ll deny the request.
So how would you go about overriding the credentials sent by the WCF adapter in BizTalk? Should you be presented with this problem in a regular .NET run time environment (such as a console application or windows service) you’d probably end up doing it in one of the following ways:
- Creating a class which inherits the ClientCredentials (MSDN link) type in which you would explicitly set the Windows.ClientCredential.Username property along with the Windows.ClientCredential.Password property and the Windows.ClientCredential.Domain property to what ever you like. This type would then be pointed out in your WCF client configuration as the type to be used by the clientCredentials behavior extension for providing credentials (see this link for documentation about the ClientCredentialsElement.Type property, and see this screenshot for an explanation of how to set it up).
- Another way of doing it is to do it directly on your generated client proxy within your applications code. A slightly more hard coded approach, but certainly viable:
serviceProxy.ClientCredentials.Windows.ClientCredential.UserName = “HelloKitty”;
Trying to send a SOAP message from your regular .NET application using any of these methods will result in the WCF client sending the correct (as in, desired) credentials to the server and thus succeeding in authenticating.
Why all the fuss with putting the user name and password in code and not in the WCF section (<system.serviceModel>) of your configuration file? Well, it’s been decided that giving the option of declaring credentials in a configuration file is insecure and therefore should not be supported out of the box. Not a totally stupid thought, actually. Worth noting here is that your code implementing any of the two solutions above could of course read the user name and password from anywhere you want – a database, a text file, basically anything.
Considering that the actual WCF configuration in BizTalk allowed me to specify a custom type for providing clientCredentials (as seen in this screenshot) I decided to go with the first alternative. Now that I had created a type that inherits the ClientCredentials (MSDN link) type I added it to the GAC, I added the fully qualified assembly name to the type-property of the clientCredentials extension for the newly created client behavior which I had added to the endpoint (screenshot shown again here). After doing all these things the setup is ready for another test. Nota bene: these configuration options require you to use WCF-Custom as your port type.
This test will fail! The Event Viewer will have an entry in its Application log saying this:
System.InvalidOperationException: The ClientCredentials cannot be added to the binding parameters because the binding parameters already contains a SecurityCredentialsManager ‘System.ServiceModel.Description.ClientCredentials’. If you are configuring custom credentials for the channel, please first remove any existing ClientCredentials from the behaviors collection before adding the custom credential.
Yes, this will be confusing to you (and me) since we in the configuration are only allowed to specify one custom type for providing our ClientCredentials. So where did the other instance come from, and why did it get there before our custom one?
Thinking about this I came to the following conclusion: the developers of BizTalk had a problem they needed to overcome. Remember what I said about storing user names and passwords in WCF configuration files? Since this was deemed to be unsafe by the WCF team this meant that the developers building the WCF adapter for BizTalk weren’t able to solely rely on the WCF configuration file structure to provide all the data needed for the WCF port to actually execute, especially if any credentials would be required by the service in the other end. You might also remember that you, as a developer configuring the WCF options of your WCF port in BizTalk, are allowed to enter credentials if “basic” is chosen as the authentication method. My guess here is that those credentials entered in that dialog end up somewhere in the SSO database used by BizTalk. But how do they get from the SSO database to the WCF runtime when the WCF port needs to send a message?
This is where the secret sauce enters the stage! The only explanation I’ve come up with is that the wrapper code for the WCF adapter in BizTalk dynamically generates a ClientCredentials instance which is populated with any data entered by the BizTalk administrator in the port configuration. This instance is then added to the generated WCF client in BizTalk and thus disabling any other instances of the same type to be added to the same client.
So, to sum this up I’d say the following:
- The credentials implementation for the WCF adapter in BizTalk 2006 R2 is broken. Possibly in more ways than one:
- It won’t allow you to successfully specify user name and password for NTLM authentication.
- It won’t allow you to successfully specify a custom type providing credentials for NTLM authentication.
- It seems to have a hard coded behavior where it insists on sending the host processes’ account details as authentication credentials when NTLM is chosen as the authentication method. This may be by design or standard – I don’t know.
- The decision to deny developers the ability to store credentials in an application configuration file is not bad – but it should be more “out in the open” that there is a security reason behind this decision. During these days I’ve seen dozens of forum threads with people trying to figure out why they can’t find a place to configure their user name and password for the service they’re accessing, all of them assuming that they have missed something, somewhere.
The solution (which I said I didn’t have)?
Use any other authentication mechanism than NTLM, atleast if the host process account has no chance of being granted access to the target service. Or try out the linked article in the beginning of this post. Good luck!
Share
Entries (RSS)
I succeeded in getting around the System.InvalidOperationException “… already contains a SecurityCredentialsManager …remove any existing ClientCredentials…” exception by overriding the ClientCredentials AddBindingParameters method:
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters)
{
if (bindingParameters == null)
{
throw new ArgumentNullException(“bindingParameters”);
}
SecurityCredentialsManager manager = bindingParameters.Find();
if (manager != null)
{
bindingParameters.Remove(manager);
}
bindingParameters.Add(this);
}
This detects the BizTalk dynamically generated ClientCredentials, removing it before mine is added.
My need for the custom ClientCredentials was to separate the SSL client certificate from the client signing certificate. All of this required a custom ClientCredentials, custom SecurityTokenManager and others. I use BizTalk Server 2010.