Authentication Challenge Method Is Not Called When Using NSURLSession Custom Delegate
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
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>
Comments
-
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.