iOS: HTTP Basic/Digest Auth with a UIWebView
Asked Answered
L

0

6

Overview

I'm working on a SAML login (single sign-on, similar to openID) solution for an iOS app that involves showing a view controller with a UIWebView and I'm running into a timing and/or timeout issue when handling HTTP basic/digest auth in the UIWebView.

Specifically, when the client gets an HTTP auth challenge, I pop an UIAlertView prompting the user for a userID & password. If the user is able to enter the info quickly (< 10 seconds), it works. However, if the entry takes more than 10 seconds, the connection appears to have been terminated and nothing happens.

Questions

  1. Is there a timeout on calls to connection:didReceiveAuthenticationChallenge: that would prevent me from prompting the user for a userID & password (and having to wait for user input)? Does anyone have a workaround (e.g. some way to extend the connection timeout)?
  2. Is there a better way to handle HTTP basic/digest auth from a UIWebView than a subclass of NSURLProtocol?

Details & Code

For most of the SAML systems we need to handle, the login will appear as a regular web page in the UIWebView. However, some of the systems we need to handle fall back to using HTTP basic or HTTP digest authentication for mobile browsers, so we need to be able to handle that as well.

The big challenges start with the fact that UIWebView does not expose the network calls underneath. To get at what I need, I've created a subclass of NSURLProtocol and registered it, as necessary:

[NSURLProtocol registerClass:[SMURLProtocol class]];

With that, this method on SMURLProtocol gets called when an HTTP basic/auth challenge is issued, so I return YES we can handle HTTP basic & digest authentication:

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

Now I've told the networking stack that SMURLProtocol can handle the auth challenge, 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'm launching an UIAlertView to prompt the user for a userID and password. I have to do that back on the main thread because (apparently, I don't know for certain) the networking code is running on a background thread. I added the semaphore and explicit Grand Central Dispatch code to work around occasional crashes I was seeing (based upon this thread).

The final piece is the UIAlertView delegate that accepts the userID & password builds the credential for the challenge:

- (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 overview, this all works great if the user is able to input the userID & password in less than about 10 seconds. If it takes longer, the connection appears to get timed out and passing the credentials on to the challenge's sender has no effect.

Loni answered 11/1, 2013 at 18:59 Comment(3)
Did you ever get an answer? I have the very same problem except with AFNetworking (which is just a pretty wrapper over NSURLConnection). #15847195Frustum
After much consternation, I've discovered that performing SAML authentication inside of a UIWebView (i.e. inside of your app) is a Sisyphean task. The much cleaner, robust, but not as user friendly route is to leverage Safari to do this for you. Basically, you launch over to Safari with the authentication URL and, after the service is done, it launches back to your app with a custom URL scheme and passes the authentication token as a URL parameter.Loni
Apple's current, official recommendation for OAuth/SAML authentication flows is to use SFSafariViewController (introduced in iOS 9). However, most apps that perform OAuth/SAML (including ours) are still using Safari because it is the most reliable and robust way to handle the odd authentication configurations found in many enterprises (e.g. self signed certs, digest auth, etc.).Loni

© 2022 - 2024 — McMap. All rights reserved.