Authentication Challenges and TLS Chain Validation

An NSURLRequest object often encounters an authentication challenge, or a request for credentials from the server it is connecting to. The NSURLSession class notifies its delegates when a request encounters an authentication challenge, so that they can act accordingly.

Deciding How to Respond to an Authentication Challenge

If a session task requires authentication, and there are no valid credentials available, either as part of the requested URL or in the shared NSURLCredentialStorage, it creates an authentication challenge. It first sends URLSession:task:didReceiveChallenge:completionHandler: to its task delegate to handle the authentication challenge. If the task delegate does not respond to that message, the task sends URLSession:task:didReceiveChallenge:completionHandler: to its session delegate to handle the authentication challenge.

In order for the connection to continue, the delegate has three options:

To help determine the correct course of action, the NSURLAuthenticationChallenge instance passed to the method contains information about what triggered the authentication challenge, how many attempts were made for the challenge, any previously attempted credentials, the NSURLProtectionSpace that requires the credentials, and the sender of the challenge.

If the authentication challenge has tried to authenticate previously and failed (for example, if the user changed his or her password on the server), you can obtain the attempted credentials by calling proposedCredential on the authentication challenge. The delegate can then use these credentials to populate a dialog that it presents to the user.

Calling previousFailureCount on the authentication challenge returns the total number of previous authentication attempts, including those from different authentication protocols. The delegate can provide this information to the user, to determine whether the credentials it supplied previously are failing, or to limit the maximum number of authentication attempts.

Responding to an Authentication Challenge

The following are the three ways you can respond to the URLSession:didReceiveChallenge:completionHandler: or URLSession:task:didReceiveChallenge:completionHandler: delegate methods.

Providing Credentials

To attempt to authenticate, the application should create an NSURLCredential object with authentication information of the form expected by the server. You can determine the server’s authentication method by calling authenticationMethod on the protection space of the provided authentication challenge. Some authentication methods supported by NSURLCredential are:

  • HTTP basic authentication (NSURLAuthenticationMethodHTTPBasic) requires a user name and password. Prompt the user for the necessary information and create an NSURLCredential object with credentialWithUser:password:persistence:.

  • HTTP digest authentication (NSURLAuthenticationMethodHTTPDigest), like basic authentication, requires a user name and password. (The digest is generated automatically.) Prompt the user for the necessary information and create an NSURLCredential object with credentialWithUser:password:persistence:.

  • Client certificate authentication (NSURLAuthenticationMethodClientCertificate) requires the system identity and all certificates needed to authenticate with the server. Create an NSURLCredential object with credentialWithIdentity:certificates:persistence:.

  • Server trust authentication (NSURLAuthenticationMethodServerTrust) requires a trust provided by the protection space of the authentication challenge. Create an NSURLCredential object with credentialForTrust:.

After you’ve created the NSURLCredential object, pass the object to the authentication challenge’s sender using the provided completion handler block.

Continuing Without Credentials

If the delegate chooses not to provide a credential for the authentication challenge, it can attempt to continue without one. Pass one of the following values to the provided completion handler block:

Canceling the Connection

The delegate may also choose to cancel the authentication challenge, by passing NSURLSessionAuthChallengeCancelAuthenticationChallenge to the provided completion handler block.

An Authentication Example

The implementation shown in Listing 4-1 responds to the challenge by creating an NSURLCredential instance with a user name and password supplied by the application’s preferences. If the authentication has failed previously, it cancels the authentication challenge and informs the user.

Listing 4-1  An example of using the URLSession:didReceiveChallenge:completionHandler: delegate method

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if ([challenge previousFailureCount] == 0) {
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
                                                                    password:[self preferencesPassword]
                                                                 persistence:NSURLCredentialPersistenceNone];
        completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
    } else {
        // Inform the user that the user name and password are incorrect
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    
    let proposedCredential = URLCredential(user: self.preferencesName, password: self.preferencesPassword, persistence: .none)
    completionHandler(.useCredential, proposedCredential)
}

If the authentication challenges goes unhandled by the session or task delegate, and credentials are not available or if they fail to authenticate, a continueWithoutCredentialForAuthenticationChallenge: message is sent by the underlying implementation.

Performing Custom TLS Chain Validation

In the NSURL family of APIs, TLS chain validation is handled by your app’s authentication delegate method, but instead of providing credentials to authenticate the user (or your app) to the server, your app instead checks the credentials that the server provides during the TLS handshake, then tells the URL loading system whether it should accept or reject those credentials.

If you need to perform chain validation in a nonstandard way (such as accepting a specific self-signed certificate for testing), your app must implement either the URLSession:didReceiveChallenge:completionHandler: or URLSession:task:didReceiveChallenge:completionHandler: delegate method. If you implement both, the session-level method is responsible for handling the authentication.

Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust, and if so, obtain the serverTrust information from that protection space.