IOS: HTTP Basic / Digest Auth with UIWebView

Overview

I am working on a SAML login solution (single-user login similar to openID) for an iOS application that includes showing a view controller with UIWebView , and I ran into a time and / or timeout problem while processing HTTP basic / digest auth in UIWebView .

In particular, when a client receives an HTTP auth call, I exit UIAlertView , asking the user for a user ID and password. If the user can quickly enter information (<10 seconds), it works. However, if the recording takes more than 10 seconds, the connection seems to be completed and nothing happens.

Questions

  • Is there a timeout for calls to connection:didReceiveAuthenticationChallenge: that prevents me from asking the user for a user ID and password (and waiting for user input)? Does anyone have a workaround (e.g. some way to extend the connection timeout)?
  • Is there a better way to handle HTTP basic / digest auth with a UIWebView than a subclass of NSURLProtocol ?

Details and code

For most SAML systems that we need to process, the login will appear as a normal web page in UIWebView . However, some of the systems we need for processing are reverting to using basic HTTP or HTTP authentication for mobile browsers, so we should also be able to handle this.

Big problems start with the fact that UIWebView does not open network calls under it. To get what I need, I created a subclass of NSURLProtocol and registered it if necessary:

 [NSURLProtocol registerClass:[SMURLProtocol class]]; 

At the same time, this method on SMURLProtocol is called when HTTP basic / auth is called, so I return YES, we can handle authentication of the main and HTTP digests:

 - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]); } 

Now I told the network stack that SMURLProtocol can handle the auth call, so it calls

 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; NSString *authenticationMethod = [protectionSpace authenticationMethod]; if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest]) { // Stash the challenge in an IVAR so we can use it later _challenge = challenge; // These network operations are often on a background thread, so we have to make sure to be on the foreground thread // to interact with the UI. We tried the UIAlertView performSelectorOnMainThread, but ran into issues, so then // we switched to GCD with a semaphore? _dsema = dispatch_semaphore_create(0); dispatch_async(dispatch_get_main_queue(), ^{ // Prompt the user to enter the userID and password UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"AUTHENTICATION_REQUIRED", @"") message:[protectionSpace host] delegate:self cancelButtonTitle:NSLocalizedString(@"CANCEL", @"") otherButtonTitles:NSLocalizedString(@"LOG_IN", @""), nil]; [alert setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput]; [alert show]; }); dispatch_semaphore_wait(_dsema, DISPATCH_TIME_FOREVER); // --> when you get here, the user has responded to the UIAlertView <-- dispatch_release(_dsema); } } 

As you can see, I run UIAlertView to request the user id and password from the user. I have to do this on the main thread, because (apparently, I don’t know for sure) the network code runs on the background thread. I added a semaphore and explicit Grand Central Dispatch code to handle the random crashes I saw (based on this thread ).

The last part is the UIAlertView delegate, which accepts the user ID and password, builds the credentials for the call:

 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (([alertView alertViewStyle] == UIAlertViewStyleLoginAndPasswordInput) && (buttonIndex == 1)) { NSString *userID = [[alertView textFieldAtIndex:0] text]; NSString *password = [[alertView textFieldAtIndex:1] text]; // when you get the reply that should unblock the background thread, unblock the other thread: dispatch_semaphore_signal(_dsema); // Use the userID and password entered by the user to proceed // with the authentication challenge. [_challenge.sender useCredential:[NSURLCredential credentialWithUser:userID password:password persistence:NSURLCredentialPersistenceNone] forAuthenticationChallenge:_challenge]; [_challenge.sender continueWithoutCredentialForAuthenticationChallenge:_challenge]; _challenge = nil; } } 

As I said in the review, all this works fine if the user can enter the user ID and password in less than 10 seconds. If this takes longer, the connection appears to get a timeout and passing the credentials to the sender of the call has no effect.

+6
source share

All Articles