WCF and Domain Trust Failure

Lately users have been getting the following error when accessing a WCF services that uses Windows authentication.

"The trust relationship between the primary domain and the trusted domain failed."

Not all users are getting this error despite that they all belong to the same domain.

Trying to find a solution I came up with three options:

  • Remove any inactive trusted domains from Active Directory
  • In the local security policy of the server change the cache value of the “Interactive Logon: Number of previous logons to cache” to 0 (zero)
  • Change the unhandled exception policy back to the default behavior of previous .NET Framework versions.

As we needed a quick fix, the tech guys picked the last option and added the following lines to the Aspnet.config in the %WINDIR%\Microsoft.NET\Framework\v2.0.50727 directory:

<configuration>
<
runtime>

<legacyUnhandledExceptionPolicy enabled="true" />

</runtime>
</
configuration>

After restarting the server the users could once again access the service.

I believe the second option of disabling the logons cache would be a better solution, but that one it yet to be tested.

Advertisements

WCF: Windows Authentication and External Users

Using Windows authentication with WCF enables developers to support centralized access management for enterprise services. As default the framework uses the computer login credentials of the Windows user accessing the service.

This default behavior works for all the cases where the computer belongs to the same domain as the server, e.g. when using a company computer. But a when external users needs to access a service that uses Windows authentication, the client needs to send credentials the server can identify. For this you need to define the credentials prior to creating the channel.

factory.Credentials.Windows.ClientCredential =
new System.Net.NetworkCredential(name, password, domain);

 
Make sure not to mix the factory.Credentials.Windows.ClientCredential and factory.ClientCredential properties.

There is also a good resource about debugging Windows authentication errors at MSDN.

Test Run Deployment Issue

I replaced one of my test files with an image from the internet. I was a bit surprised when I got the following error when trying to run a test that was working just minutes before:

Failed to queue test run ‘MYCOMPUTER 2008-12-08 15:11:35’: Test Run deployment issue: The location of the file or directory ‘Tests\Material\TestPic.jpg’ is not trusted.

 

I then googled around and found the following thread on the MSDN forums: http://social.msdn.microsoft.com/Forums/en-US/vststest/thread/1a9c13b3-e8b9-4619-9159-3edbfc67b8a9/

 

What you need to do is unblock the file. Go to your deployment files folder and open the file properties:

 

Blocked file

 

 

Notice the security note on the bottom. Click on the Unblock button and also uncheck the readonly checkbox.

 

After pressing OK Visual Studio will again accept the file.

 

 

WCF and Windows Security Revisited

Configuring WCF to require Windows authentication is a pretty trivial task. But this alone does not set any requirements for what the clients credentials need to be. If you want the clients to belong to a certain Active Directory group, you are required to do some coding to achive it.

One option is to define PrincipalPermission attributes in every class to define its security requirements. But this means hard coding the requirements. The only flexible solution I found was to write my own  ServiceAuthorizationManager and read the group name from the app.config.

public class CustomAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{

    // For mex support
    if (operationContext.ServiceSecurityContext.IsAnonymous)
        return true;

    // When Windows authentication has been setup using an application setting
    if (Properties.Settings.Default["UserGroup"] != null)
    {

        WindowsIdentity identity = operationContext.ServiceSecurityContext.WindowsIdentity;

        if
(!identity.IsAuthenticated)
            throw new SecurityTokenValidationException("Windows authentication required");

        WindowsPrincipal principal = new WindowsPrincipal(identity);
        string group = Properties.Settings.Default["UserGroup"].ToString();

        return principal.IsInRole(group);

    } else {

        return base.CheckAccessCore(operationContext);}

    }

}

In the service app.config:

<serviceBehaviors><behavior name="Service1Behavior">
...
<serviceAuthorization serviceAuthorizationManagerType="CustomAuthorizationManager, MyAssembly"/>
</
behavior>
</
serviceBehaviors>

Working Web Services

Working with web services in .NET (starting from 2.0) should be as easy as 1-2-3. Tools are provided for auto-generating code based on a WSDL. With .NET 3 Microsoft tries to direct every developer to use WCF for handling web services. Trying to get a Axis based Java web service to work proved to require some tricks.

I first generated a web service client (proxy) using the Add Service Reference tool in Visual Studio. This got a me a WCF based class to interact with the web service. All this was easy, but when I tried to call one of the remote methods, I was literally left with nothing (null).

As the diagnostic features (message tracking and tracing) of WCF really didn’t reveal anything usefull, I had to implement my own custom behavior with a message inspector to see what was really happening behind the secenes. This revealed that messages where sent and received, but the data did not get processed by the client. In the end, there wasn’t much else to do than to fall back and to make use of the older .NET web service handling technology (System.Web.Services).

Once again generating a client based on the WSDL was easy. And when I now tried calling the remote function I was returned with the data I expected. So, once again legacy technology prevailed when the newer failed.

As authentication (basic HTTP) is required by most we services I needed my client to support this. Normally this should only require to supply some valid credentials:

MatrixWebServicesServiceproxy = new MatrixWebServicesService();
proxy.PreAuthenticate =
true;
proxy.Credentials =
new NetworkCredential("user", "password");

But this did not do the trick. Using a TCP sniffer I was able to see that the “Authorized” HTTP header did not get sent to the server. After some research I came to the following conclution: The .NET client (this also applies to WCF) is designed to expect a “Unauthorized” HTTP error (401) from the server to respond to, without that the credentials are not sent. Also setting the PreAuthenticate property to true will only force the client to send the credentials with every message after this has first been required by the server. The problem with the Java web service was that is sent a “Internal Server Error” HTTP error (501) and a valid SOAP error when the authentication failed. So the credentials where never sent.

The only solution I found for this was to modify the generated client code and force it to send the credentials with every message. This can be done by overriding the clients GetWebRequest method. As the client is based on a bunch of partial classes, you only need to add your own with the following code to get things running:

public partial class SomeJavaWebServices : System.Web.Services.Protocols.SoapHttpClientProtocol {

protected override WebRequest GetWebRequest(Uri uri)
{
    HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(uri);

    if (PreAuthenticate)
   
{

        NetworkCredential networkCredentials = Credentials.GetCredential(uri, “Basic”);
        if (networkCredentials != null)
        {

            byte[] credentialBuffer = new System.Text.UTF8Encoding().GetBytes(networkCredentials.UserName + “:” + networkCredentials.Password);

            request.Headers[“Authorization”] = “Basic “ + Convert.ToBase64String(credentialBuffer);
        }
        else
        {
            throw new ApplicationException(“No network credentials”);
        }
    }    return request;
}

 

}

WCF and Windows Security

Working on my first real WCF service project I encountered problems with getting the client to connect to the host when running it on another computer and a different user. This was probably challenging only because of my inexperience with the technology and the overwhelming amount of documentation (but very little discussion) on the subject.

When you want to use Windows security with a netTcpBinding, you must first configure the client and the host to do so:

<security mode="Transport">
<
transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>

This was pretty trivial.

The magic lays behind the identity configuration of the client endpoint. I’m still not sure why this is required, but to get Windows authentication to work, you need to define a correct User Principal Name (UPN) or Service Princial Name (SPN). 

So, if the host is running with user credentials, you should use its UPN:

<identity>
<
userPrincipalName value="user@some.com" />
</identity>

And if the host is running as s service, define a SPN:

<identity>
<
servicePrincipalName value="Host/MYCOMPUTER" />
</identity>

Host/<server> is the default SPN, but a domain administartor can also create a service specific one.

What I noticed when trying different configurations, was that if I leave the UPN/SPN value blank (or entered any value), the client will for some reason connect. This has to do something with the fact that as default Kerberos is used for the authentication, but if that fails NTLM takes over. So to make sure your settings are correct, try the following:

<client>
<endpoint behaviorConfiguration = "clientEndpointCredential">
...
</
client>
<
behaviors>
<
endpointBehaviors>
<
behavior name="clientEndpointCredential">
<
clientCredentials>
<
windows allowNtlm="false" />
</
clientCredentials>
</
behavior>
</
endpointBehaviors>
</
behaviors>

Comprehensive guide to WCF security: http://www.codeplex.com/WCFSecurityGuide