Authentication Challenge Method Is Not Called When Using NSURLSession Custom Delegate

13,497

Solution 1

There are two different challenge/response handlers in NSURLSession's delegates. The first, which you're implementing, is at the session level, and basically handles server-level authentication. From the documentation:

  • For session-level challenges—NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate, or NSURLAuthenticationMethodServerTrust—the NSURLSession object calls the session delegate’s URLSession:didReceiveChallenge:completionHandler: method. If your app does not provide a session delegate method, the NSURLSession object calls the task delegate’s URLSession:task:didReceiveChallenge:completionHandler: method to handle the challenge.

  • For non-session-level challenges (all others), the NSURLSession object calls the session delegate’s URLSession:task:didReceiveChallenge:completionHandler: method to handle the challenge. If your app provides a session delegate and you need to handle authentication, then you must either handle the authentication at the task level or provide a task-level handler that calls the per-session handler explicitly. The session delegate’s URLSession:didReceiveChallenge:completionHandler: method is not called for non-session-level challenges.

So, you probably want to handle task-level authentication by adding protocol support for NSURLSessionTaskDelegate in your delegate object, and supplying a handler at the task level, i.e. URLSession(_:task:didReceiveChallenge:completionHandler:).

Solution 2

From iOS developer library

Important: The URL loading system classes do not call their delegates to handle request challenges unless the server response contains a WWW-Authenticate header.

From where we understand that didReceiveChallenge methods should be called BUT the method URLSession:task:didReceiveChallenge:completionHandler: gets called only when the header looks like 'WWW-Authenticate':'Basic'

I haven't found any solution how to handle token based authentication where header is 'WWW-Authenticate':'Bearer' as in question using Authentication Challenges.

Solution 3

if You write:

AuthChallengeDelegate *authChallengeDel = [[AuthChallengeDelegate alloc] init];

authChallengeDel is LOCAL so will be released as soon as you exit methods. So make it a property

Solution 4

I know this issue is related to iOS 8 but if you are using iOS 9 please also keep in mind that the NSAppTransportSecurity key in your Info.plist is now mandatory for targeting any specific domain names.

I was having the exact same issue and used wireshark to look at the TLS network layer to check what version of TLS was used by the image server I was targeting.

Based on that you can add this to your Info.plist and that can hopefully solves this problem

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>yourdomain.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
      <key>NSTemporaryExceptionMinimumTLSVersion</key>
      <string>TLSv1.0</string>
    </dict>
  </dict>
</dict>
Share:
13,497
Dean
Author by

Dean

JavaScript, .NET, Flutter Dev

Updated on June 16, 2022

Comments

  • Dean
    Dean almost 2 years

    I'm working on an iOS app which connects to an ASP.NET Web API through Restful services. I want to use a custom delegate to handle authentication challenge. But the delegate method doesn't get called.

    The http request is written in the following method within a view controller:

    - (IBAction)test:(UIButton *)sender
    {
        //Get Bearer Token
        KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]      initWithIdentifier:@"BearerToken" accessGroup:nil];
        NSString *bearerToken = [keychainItem objectForKey:(__bridge id)(kSecValueData)];
    
        //Configure request
        NSURL *url = [NSURL URLWithString:@"......"]; //Replace the .... with real IP Address
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        [request setHTTPMethod:@"GET"];
        [request setValue:[NSString stringWithFormat:@"Bearer %@", bearerToken] forHTTPHeaderField:@"Authorization"];
    
        //Configure session
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    
        AuthChallengeDelegate *authChallengeDel = [[AuthChallengeDelegate alloc] init];
    
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                              delegate:authChallengeDel
                                                         delegateQueue:nil];
    
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
        [task resume];
    }
    

    In the AuthChallengeDelegate class, I have implemented the following method:

    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
    {
        NSLog(@"%@", response);   
    }
    
    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                                 NSURLCredential *credential))completionHandler
    {
        NSLog(@"did receive challenge method called");
        NSLog(@"%@", challenge.protectionSpace.authenticationMethod);
    }
    

    The first method (didReceiveResponse) get called and the response status code is 401 with "Www-Authenticate" = Bearer in the header field. But the second method (didReceiveChallenge) is not called. Anyone here could give me an idea of why it's not called?

    (I'm using Xcode 6 and simulating in iOS8)

    Thanks.