WCF: Suppress multiple 401s in windows authentication
This case study is about discussing 401 challenges in case of windows authentication for WCF service consumers. It covers the perspectives from both IIS hosted and self-hosted service applications. My intention is to show case on suppressing multiple 401s in windows authentication.
WCF service application
- Hosted on IIS 7.5
- Windows authentication is enabled with Providers order Negotiate, NTLM respectively
- Binding configuration
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
- Application pool identity is set to NetworkService account
Client application – console
- App.config
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1">
<security mode="Transport">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
<client>
<endpoint address="https://myserver:2092/WcfPersistnonnNtlmService/Service1.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
- Program.cs
static void Main(string[] args)
{
// initialize proxy
var p = new ServiceReference1.Service1Client("BasicHttpBinding_IService1");
// make service method calls in a loop
for (int i = 0; i <= 2; i++)
{
Console.WriteLine(p.GetData(i));
}
// close the proxy
p.Close();
Console.ReadLine();
}
- When we call this client application from a remote machine and simultaneously allow Fiddler to capture traffic, we will see the following sequence of events are recorded.
Observation
- The client application went through the service method calls 3 times.
- Each service method call is preceded by a 401 challenge. Why?
- 1st call: Client did not pass any token. Hence, service marked the request as unauthorized – resulted in http status code 401.
- 2nd call: Client passed Negotiate token. Hence, service accepted and processed the request – resulted http status code 200
- It means client will have to pay a penalty for every request.
- Over here, client is expected to make 3 communications to server. However, it ended in 3*2 = 6 number of communications.
- Each 200 request has passed a Negotiate token.
Let us think on a larger perspective. If expected number of calls from client to server machines are more, then client will have to bear 2 times of the expected number of communication. This could cause performance bottleneck when network bandwidth is less. So, the ideal next step from application side will be to verify whether there are any ways we can reduce the traffic. Yes, there is a way.
1. Go to the IIS application
2. Select Configuration Editor under Management [in Features view]
3. Set authPersistNonNTLM to true
4. Apply
Now, let us go to the client application and observe the communication in Fiddler.
It resulted in the following:
There is only one 401 and 3 normal service calls are followed by.
If we will observe the 1st 200 request, we can say it has a negotiate token passed for authorization.
If we will observe the 2nd, 3rd onwards 200 requests, we will not see any negotiate token passed.
How does service authenticate the request then?
When authPersistNonNTLM set to true
1. It changes Kerberos authentication from request based to session based
2. TCP session is used to identify the authenticated client
More details can be found the article (blogs.msdn.com/b/benjaminperkins/archive/2011/10/31/kerberos-authpersistnonntlm-authentication-request-based-vs-session-based-authentication.aspx).
Self-hosted
Can the similar improvements (as of IIS) be achieved for self-hosted WCF service application?
- There is no way to suppress Kerberos negotiation for a self-hosted WCF service application.
- However, there is a way for NTLM authentication which will behave the same as IIS configuration change. The authPersistNonNTLM setting seems to be something specific to IIS.
- Looking at HttpListener's properties, they have a similar setting for NTLM called UnsafeConnectionNtlmAuthentication, but there is no such setting for Kerberos.
- WCF supports the NTLM setting on HttpTransportBindingElement, but there’s no Kerberos setting for us to hook into.
Case 1
- Service is self-hosted the following binding configuration.
<customBinding>
<binding name="myBinding">
<textMessageEncoding />
<transactionFlow />
<httpsTransport authenticationScheme="Negotiate" />
</binding>
</customBinding>
- When we run the client application on a remote machine, fiddler indicates the http traffic like:
- We can see there are three 401s before a 200.
- Each success http request (200) ensures that it passes a Kerberos negotiate token.
Case 2
- Service is self-hosted the following binding configuration.
<customBinding>
<binding name="myBinding">
<textMessageEncoding />
<transactionFlow />
<httpsTransport authenticationScheme="Ntlm" unsafeConnectionNtlmAuthentication="true" />
</binding>
</customBinding>
- When we run the client application on a remote machine, fiddler indicates the http traffic like:
- We can say there are 401s only 1 time.
- The 1st success call (http status code 200) only presents the NTLM authorization token.
- If we observe the 2nd / 3rd http requests, they do not present any authorization token.
- Because of the httpsTransport property unsafeConnectionNtlmAuthentication, NTLM authentication is performed once on each TCP operation. Hence, we made it TCP based now.
- It means TCP session preserved the past NTLM authorization token.
Just an FYI on a generic traffic flow:
Network -> TCP -> HTTP
- This time client application had to bear one time overhead of 401.
- Hence, network traffic is optimized now.
I hope this helps.