iosnsurlsessionnsurlprotocolnsurlsessiondatatask

NSURLSessionTask authentication challenge completionHandler and NSURLAuthenticationChallenge client


I am implementing a custom NSURLProtocol, and internally want to use NSURLSession with data tasks for internal networking instead of NSURLConnection.

I have hit an interesting problem and wonder about the internal implementation of the challenge handler of NSURLSession/NSURLSessionTask.

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                            didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                              completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

Here I am basically provided with two different challenge handlers, one being the completionHandler block, which is provided with all necessary information to handle the challenge, but there is also the legacy NSURLAuthenticationChallenge.client which has methods that correspond pretty much one to one with the completionHandler information options.

Since I am developing a protocol, and would like to pass certain authentication challenges upward the URL loading system for the calling API to implement, I need to use the NSURLSession client method:

- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

My question is whether the internal implementation of the completionHandler and NSURLAuthenticationChallenge.client is the same, and if so, can I skip calling the completion handler in the delegate method, with expectance that the URL loading system will call the appropriate NSURLAuthenticationChallenge.client method?


Solution

  • To answer my own question, the answer is no. Moreover, Apple's provided challenge sender does not implement the entire NSURLAuthenticationChallengeSender protocol, thus crashing when client attempts to respond to challenge:

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFURLSessionConnection performDefaultHandlingForAuthenticationChallenge:]: unrecognized selector sent to instance 0x7ff06d958410'
    

    My solution was to create a wrapper:

    @interface CPURLSessionChallengeSender : NSObject <NSURLAuthenticationChallengeSender>
    
    - (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
    
    @end
    
    @implementation CPURLSessionChallengeSender
    {
        void (^_sessionCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential);
    }
    
    - (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        self = [super init];
    
        if(self)
        {
            _sessionCompletionHandler = [completionHandler copy];
        }
    
        return self;
    }
    
    - (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        _sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, credential);
    }
    
    - (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        _sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, nil);
    }
    
    - (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    {
        _sessionCompletionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
    
    - (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        _sessionCompletionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    }
    
    - (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        _sessionCompletionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
    }
    
    @end
    

    And I replace the challenge object with a new one, using my wrapped sender:

    NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CPURLSessionChallengeSender alloc] initWithSessionCompletionHandler:completionHandler]];
    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];