Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't reauthenticate with different NSURLCredentials (old deleted ones are used)

i've been searching stackoverflow, google, apple and other places. The tips provided look promising, i implemented them but alltogether don't seem to work or get enforced.

Problem: I have an NSURLConnection with specific credentials. I then have a logout where I clear the credentials, the protectionspace, i remove all cached responses and delete all cookies in the sharedHTTPCookieStorage but when calling my authenticated request again a few seconds later even with wrong credentials I still am using the old (deleted) credentials

Here are some code extracts, where credentials are removed

        NSDictionary *credentialsDict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials];

    if ([credentialsDict count] > 0) {
        // the credentialsDict has NSURLProtectionSpace objs as keys and dicts of userName => NSURLCredential
        NSEnumerator *protectionSpaceEnumerator = [credentialsDict keyEnumerator];
        id urlProtectionSpace;

        // iterate over all NSURLProtectionSpaces
        while (urlProtectionSpace = [protectionSpaceEnumerator nextObject]) {
            NSEnumerator *userNameEnumerator = [[credentialsDict objectForKey:urlProtectionSpace] keyEnumerator];
            id userName;

            // iterate over all usernames for this protectionspace, which are the keys for the actual NSURLCredentials
            while (userName = [userNameEnumerator nextObject]) {
                NSURLCredential *cred = [[credentialsDict objectForKey:urlProtectionSpace] objectForKey:userName];
                WriteLog(@"Method: switchView removing credential %@",[cred user]);
                [[NSURLCredentialStorage sharedCredentialStorage] removeCredential:cred forProtectionSpace:urlProtectionSpace];
            }
        }
    }

I then remove all cached responses

    NSURLCache *sharedCache = [NSURLCache sharedURLCache];
    [sharedCache removeAllCachedResponses];

I then delete all cookies

    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    NSArray *cookies = [cookieStorage cookies];
    for (NSHTTPCookie *cookie in cookies) {
        [cookieStorage deleteCookie:cookie];
        NSLog(@"deleted cookie");
    }

I also tried using no cookies and other policies

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0];
[request setHTTPShouldHandleCookies:NO];
if(self.currentCookies != nil){
    [request setAllHTTPHeaderFields:
     [NSHTTPCookie requestHeaderFieldsWithCookies:nil]];
}

theConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];

I also tried this hint here on specifically storing the cookies and passing them again. http://www.hanspinckaers.com/multiple-nsurlrequests-with-different-cookies. There's another blog on the web suggesting to add a "#" to each URL in order to enforce reauthentication, which works but just does not solve the issue because I need to count on session's credentials and the ability to use totally different credentials.

Is this a bug or known issue and how do I really solve this... Put bluntly: What am I exactly doing wrong here?

This is really bugging me and keeping me from continuing my work.

I would greatly appreciate any input!

Thanks alot!

like image 829
Mike F Avatar asked Sep 19 '11 12:09

Mike F


5 Answers

Unfortunately, there does not seem to be a solution to this problem.

You can use NSURLCredentialPersistenceNone or the # trick or you can define the 'connectionShouldUseCredentialStorage' delegate method to return NO. If you do that every time and your app never persists the credentials for a session, that will force the challenge to occur on every request.

For apps that are only performing minimal requests or that end up using a session cookie for authentication, that might work OK.

For apps that are sending a large number of requests, these solutions all result in a 401 response for every single request and that extra challenge-response can add up in terms of data and performance.

It would be nice if you could persist the credential storage for the session until you needed to log out and then switch to one of the work-arounds, but that is not possible.

As soon as you store the credentials once for a session, they get cached for the entire TLS session. That results in a need to wait about 10 minutes until that session goes away.

You can read more about this issue at: http://developer.apple.com/library/ios/qa/qa1727/_index.html

That document mentions a limited work-around that involves appending a '.' to the end of the server name. I have been unable to get that working, however.

Other than that, these are the solutions I can think of:

1) Always use the NSURLCredentialPersistenceNone & connectionShouldUseCredentialStorage workaround that should generate the 401s. Add the Basic authentication header to the request, yourself. This should prevent the extra 401's while also bypassing the credential storage. The code for adding that authorization looks something like this:

    NSString *s ;
    NSString *authStr ;
    s = [NSString stringWithFormat:@"%@:%@",user,password] ;
    s = [YourBase64Encoder base64EncodingForData:[NSData dataWithBytes:[s UTF8String] length:strlen([s UTF8String])]];
    authStr = [NSString stringWithFormat:@"Basic %@",s] ;        
    [request setValue:authStr forHTTPHeaderField:@"Authorization"] ;

I don't know how this would be implemented for other authentication methods, but I presume it is possible.

2) Inform the user of the issue and ask them to restart the app

3) Implement your own low-level sockets based http retrieval mechanism that bypasses CFNetwork completely. Good luck with that :>)

like image 169
Rich Waters Avatar answered Oct 05 '22 00:10

Rich Waters


I just ran into this issue with AFNetworking. I'm working with a backend that requires Authorization to be passed in the header. However, when the user logs out of the app and attempts to log back in (even with different creds) I was getting an error from the server. My solution was to clear out my apps cookies when clearing the authheader in logout.

- (void)clearAuthorizationHeader {
    [self.manager.requestSerializer clearAuthorizationHeader];
    NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [storage cookies]) {
        [storage deleteCookie:cookie];
    }
}
like image 41
Segsfault Avatar answered Oct 05 '22 00:10

Segsfault


I've run into this issue too. Clearing NSURLCredentialStorage seems to partially work, but it seems like I have to wait a few seconds after this for it to take effect. Doing another HTTP request without waiting results in the old Authorization header being used.

I was able to fix it by passing NSURLCredentialPersistenceNone while initializing my NSURLCredential:

NSURLCredential* credentials = [[NSURLCredential alloc] initWithUser:username password:password persistence:NSURLCredentialPersistenceNone];

note: this will cause 401 Challenges on every HTTP request you make with this NSURLCredential. However this isn't an issue if you get back some cookies that keep you authenticated.

like image 35
JasonZ Avatar answered Oct 05 '22 01:10

JasonZ


For what it's worth, I'm having the same problem.

I think it's a timing issue. When using the simulator, if I wait 5-10 seconds before trying to log in again, login fails as expected. Also, when I use an actual phone, I can rarely get the problem to occur - which might be a function of the phone being slower, or might be a bug in the simulator.

like image 25
Vic Avatar answered Oct 05 '22 01:10

Vic


I know it's an old topic. However the only thing, that works for me was to use different urls for different certificates.

It worked in my application, since I have only 2 certificates (one general in application resources and one custom downloaded from internet after the user verification process). In my case there are no cookies, and no credentials to clear so none of the solutions I found on stackoverflow worked.

like image 37
Marcin Szulc Avatar answered Oct 05 '22 00:10

Marcin Szulc