Not possible for NSURLConnection to ignore shared NSURLCredentialStorage

Originator:ddribin
Number:rdar://5203544 Date Originated:15-May-2007 07:10 AM
Status:Open Resolved:
Product: Product Version:
Classification: Reproducible:
 
15-May-2007 07:10 AM Dave Dribin:
Summary: 
When NSURLConnection needs credentials to authenticate a request, it first consults the shared NSURLCredentialStorage.  If no credentials are found, the connection:didReceiveAuthenticationChallenge: delegate method gets called.  The problem is that the if credentials *are* found in the shared credential storage, the delegate has no way of overriding them.  If another app sets credentials in the shared storage, there is no way for my own app to set the credentials to a different username/password

Discussion on the macnetworkprog list:

  <http://lists.apple.com/archives/macnetworkprog//2007/May/msg00021.html>

The use case for this is integration with a web-based API protected with HTTP authentication.  A client may need to access multiple user accounts.  Or two competing clients may need to access different accounts.  My app should have complete control of the username and password used in the HTTP authentication.

Steps to Reproduce: 
* In one app, set the default credentials using setDefaultCredential:forProtectionSpace: on the shared NSURLCredentialStorage for a Basic Auth protected URL.

* In another app, try to accesss the protected URL using NSURLConnection.

Expected Results: 

In the second app, the connection:didReceiveAuthenticationChallenge: delegate should get called to allow it to set the username and password.

Actual Results: 

The username and password are pulled from the shared NSURLCredentialStorage.  The standard Keychain access dialog is presented the user, asking them to allow access by the second app.  If the user replies "Allow Once" or "Allow Always", then the username/password are pulled from Keychain.  The second app has no way to override this, and thus cannot use a different username/password credential than one already saved.

Regression: 
It's 100% reproducible.

Notes: 
The only workaround I've found is to override the behavior of NSURLCredentialStorage by hacking the Obj-C runtime.  The simplest way is to use a category:

-----
static NSMutableDictionary * sDefaultCredentials;

void initializeDefaultCredentials()
{
    sDefaultCredentials = [[NSMutableDictionary alloc] init];;
}

@implementation NSURLCredentialStorage  (SharedOverride)

- (NSURLCredential *) defaultCredentialForProtectionSpace: (NSURLProtectionSpace *) protectionSpace;
{
    return [sDefaultCredentials objectForKey: protectionSpace];
}

- (void) setDefaultCredential: (NSURLCredential *) credential
           forProtectionSpace: (NSURLProtectionSpace *) protectionSpace;
{
    [sDefaultCredentials setObject: credential forKey: protectionSpace];
}

@end
-----

The downside to this is that the original implementation of these methods become inaccesible.  Class posing or method swizzling could solve this, if needed.  But in the end, none of this runtime hacking should be necessary.

Comments

Workaround no longer works (10.5.8, 10.6+)

Just wanted to drop a quick comment that the workaround provided in this report no longer seems to work.

I've tested this on both 10.6.1 and 10.5.8, and neither defaultCredentialForProtectionSpace: nor setDefaultCredential:forProtectionSpace: seem to be called. Pausing the debugger when the keychain dialog is up shows that the URL loading system is deep in some code in CFNetwork, which includes a (c++) class named URLCredentialStorage. Presumably this is the low-level implementation for NSURLCredentialStorage. But it seems like the calls go directly to C++, which means that the category method overrides are never called.

However, the NSURLConnection delegate method -connectionShouldUseCredentialStorage: which was officially added in 10.6 seems to be called on 10.5 as well (at least on 10.5.8). I'm not sure how far back that will work, but a hybrid solution could be to use both -connectionShouldUseCredentialStorage: and the method described in Dave's workaround. My best guess is that -connectionShouldUseCredentialStorage: showed up with the Safari 4 update, but perhaps it was just one of the OS point releases.


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!