Dec 16

This post is on a very narrow issue, but one that I spent several hours tracking down and finding a fix for. Hopefully this will save some grey hairs for a few of my loyal readers and Google searchers.

An app that we’re working on connects to an IIS server and authenticates using NTLM. For security reasons (for this particular app) NTLM is the only acceptable authentication mechanism.

Therefore, in the didReceiveAuthenticationChallenge callback method we specifically check to ensure that the authentication method is the one we’re accepting.

- (void)connection:(NSURLConnection*)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge {
	
  if (([[[challenge protectionSpace] authenticationMethod] isEqual:authenticationMethod]) &&
    ([challenge previousFailureCount] <= kAllowedLoginFailures)) {
		
    [[challenge sender]  useCredential:[NSURLCredential 
                    credentialWithUser:user.username
                              password:user.password
                           persistence:NSURLCredentialPersistenceNone] 
            forAuthenticationChallenge:challenge];
  } else {
    [[challenge sender] cancelAuthenticationChallenge:challenge];
  }
}

This worked great until the app connected to a server over SSL. Then we experience a very strange behavior. Communication with the server worked fine the first time the app was launched. But after quitting the app and launching it a second time, all server communications failed.

After a bit of debugging and a liberal sprinkling of log statements we discovered that the server sometimes sent the authentication method NSURLAuthenticationMethodServerTrust instead of the expected NSURLAuthenticationMethodNTLM.

We spent some time digging deep into the settings for IIS. The idea of forcing IIS to only use NTLM authentication seemed promising. But in the end we were unable to find the right switch in IIS to get the behavior we desired.

So back to Xcode for a closer look at how authentication is handled. We soon came to the canAuthenticateAgainstProtectionSpace callback method. Which had this naïve implementation:

- (BOOL)connection:(NSURLConnection*)conn
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace*)protectionSpace {
  return YES;
}

After fixing this and rejecting authentication methods we can't or don't want to handle, the method now looks like this:

- (BOOL)connection:(NSURLConnection*)conn
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace*)protectionSpace {
  DLog(@"protectionSpace: %@", [protectionSpace authenticationMethod]);
	
  // We only know how to handle NTLM authentication.
  if([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodNTLM])
    return YES;
	
  // Explicitly reject ServerTrust. This is occasionally sent by IIS.
  if([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
    return NO;
	
  return NO;
}

The IIS server still sends NSURLAuthenticationMethodServerTrust occasionally but when the iOS app rejects it, IIS immediately follows with a request for NSURLAuthenticationMethodNTLM. The didReceiveAuthenticationChallenge method is then called with an authentication method that it can handle. All is well again.

written by Nick \\ tags: , , ,