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

6

16

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!

Rembrandt answered 19/9, 2011 at 12:45 Comment(4)
I'm having the same problem – I can't get credentials out of the storage once I've put them in. Calling -removeCredential... seems to have no effect.Hahnert
And, for what it's worth, my "fix" was to adapt my application to use the asynchronous API, which allows me to provide credentials exactly the way I choose.Hahnert
If you think removing the cookies and clearing the cache "may" not work then the other way would be to scramble the credential, user id and or password with random text so that the login fails until a correct credential is entered!Ancylostomiasis
@Annjawn: does this work for you? I haven't had any success scrambling the credentials. As described in my post what works is scrambling the URL with a "#" or random number - however this seems more of a hack than a solution.Rembrandt
P
7

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 :>)

Prairial answered 18/8, 2013 at 17:34 Comment(1)
I ended up adding the Authorization headerfield to all my requests, as you suggested. Could not find another way to avoid the problem I had, where one was able to login with wrong credentials just after logging out.Energetic
F
4

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];
    }
}
Faden answered 13/1, 2015 at 4:53 Comment(1)
you just saved my day - i was having the exact same issue w/ an oauth connection as opposed to basic, and the credentials were being re-used. clearing the cookies on logout did the trick (in addition to deleting the credential, etc)Appeasement
F
3

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.

Foundling answered 10/7, 2012 at 20:46 Comment(0)
C
1

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.

Councilwoman answered 6/10, 2011 at 1:51 Comment(1)
Ok that sounds interesting - i also do think it has something to do with timing but only in a minor way. I am not getting the same behavior as you are. I haven't dealt with this bug for some time now - i shamelessly restart my app when logging out :/ - anyhow in my case i still have the credentials for let's say 10 minutes even if i programmatically remove them. after that i'm fine. My guess on this one is that Apple or any framework working in the background is caching those credentials to the actual url and we just can't access that part. This seems to correlate with the '#' trick.Rembrandt
T
1

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.

Teeth answered 15/2, 2013 at 10:10 Comment(0)
S
0

I was facing the same problem, now it works.

Using NSURLConnection this issue can be fixed easily by adding a random number to the end of the URL:

So, search for your URLRequest and append the random number to URLRequest

NSInteger randomNumber = arc4random() % 999;
NSString *requestURL = [NSString stringWithFormat:@"%@?cache=%ld",yourURL,(long)randomNumber];

NSURL *URLRequest = [NSURL URLWithString:requestURL];

And make sure you have a random number at the end of all URLs you are calling.

Scapegoat answered 9/9, 2016 at 18:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.