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!
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 :>)
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];
}
}
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.
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.
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.
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.
Related
I have been working on an application involving client-server communication. Everything was working fine until iOS 7.1. Now that Xcode 6 GM Seed in launched, I tried to build and run the project on iOS8 simulator. However, all NSURLConnections seem to time out now. I can not get any response from the server. This works on iOS 7 simulator and Xcode 6.
I tried using the NSURLSession hoping that would resolve the issue. But it hasnt.
Any help will be highly appreciated!! If anyone else has faced this issue, please let me know if you have any workaround to this.
UPDATE:
This is the code I have used:
NSString *authStr = [NSString stringWithFormat:#"%#:%#", username, password];
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat: #"Basic %#",[authData base64EncodedStringWithOptions:0]];
[inURLRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
Please note that the inURLRequest is already iniatlized with the desired URL.
After this just use the inURLRequest to fire the transaction as usual. For eg., in case of NSURLSession, create a download task using this request object and call resume API on it.
Well, I have found a fix for this issue.
The app had basic authentication in place for each REST webservice. This means that we used to get an authentication challenge for each webservice where we sent the credentials back with the challenge.
I changed this to send the credentials in the request header (in encrypted format) as a header field.
I am not sure how this was working in iOS 7 and not in iOS 8. But this seems to have fixed my problem :).
If you are streaming data to the NSURLConnection make sure you write new data exactly once when the stream event hasSpaceAvail has arrived - I found I had connection issues when I had wrong handling of the hasSpaceAvail event.
do not write more than once in response to the event
do not write zero times (or else you won't get another hasSpaceAvail event)
I'm working on an objective-c project that downloads webpages from a community website and parses the results. The download code looks like this:
NSError* error = nil;
NSString* text = [NSString stringWithContentsOfURL:fileUrl encoding:NSASCIIStringEncoding error:&error];
if(text) {
return text;
}
else {
NSLog(#"Error = %#", error);
return nil;
}
The odd thing is that when I download from the site I see resulting content that I would only see if logged into the site (which, in my browser, I am).
Does that method (NSString stringWithContentsOfURL:encoding:error) use browser cookies when executing the request? If so, is it Safari specifically that it's integrated with? The default browser? I can't seem to find documentation describing the behavior that I'm seeing. I'm ok with the behavior (in fact, it's preferable), but I only want to depend on it if I fully understand what's going on.
Thanks for your time.
Cookies are automatically handled and stored in an app's NSHTTPCookieStorage shared instance. Call the cookies method and check to see if your cookie is there. If it is, then that confirms your suspicion.
EDIT: I highly suspect you are using a UIWebView in your app and logging in from there. In that case, then yes, cookies are stored in your app's NSHTTPCookieStorage shared instance and will be used with further URL requests.
I'm using ASIHTTPRequest API to get some JSON data from a web side. I'm using an asynchronous request without changing default cache properties. Code is as follows:
__unsafe_unretained ASIHTTPRequest * request = [ASIHTTPRequest requestWithURL:url];
request.timeOutSeconds = 30;
[request setCompletionBlock:^(void)
{
NSString *str = [request responseString];
/*
*
*
*/
}];
[request setFailedBlock:^(void)
{
NSLog(#"status: %#",[request responseStatusMessage]);
NSLog(#"%# : %#",[request url],[[request error] debugDescription]);
}];
[request startAsynchronous];
However, obtained JSON data remains as the old one although content of JSON data in server changes.
I checked data using web browser, both on laptop and on iPhone safari. When i request url after a change in JSON, it first returns old data, but if i refresh the page, it returns updated data. But in the app, ASIHTTPRequest always returns the old data.
I also try to debug ASIHTTPRequest code in order to see whether any cached data used. But it seems like it never uses download cache because it has not been set. It never enters [useDataFromCache] method.
What could be the problem? How can i force ASIHTTPRequest to check whether there is an updated data on server, and make it get the true updated JSON?
EDIT
I used Cache-Control header, and now i get the correct updated JSON data. Code is as follows:
[request addRequestHeader:#"Cache-Control" value:#"no-cache"];
However, i think from now on request will always try to retrieve JSON even if it is not modified, which will decrease performance.
How can i make it first check the server whether data is modified, and retrieve if it is modified? Currently i get JSON data as a dynamic response from a php url, so there is no file which i can check up to dateness of the data.
What could be the solution?
Regards,
Given everything you've said, it seems unlikely that ASIHTTPRequest is cacheing the response.
So, something else must be - it seems like you have a cacheing proxy server inbetween you and the server, and that's why setting Cache-Control makes a difference.
It could be a proxy server on your local network, or it could be at your ISP, or it could be in front of the web server you're using.
According to ASIHTTPRequest's documentation, calling the method
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
will clear the cache. Call this method before you send the request and it should give you the updated JSON data.
My App connects to a server and based on a cookie the server will issue a different response.
Is it no possible to programmatically clear the cookie store, so that the server will not recognize my App when it contacts the server the next time.
I gathered that clearing the Cookies in the Settings.app does only apply for cookies within Safari.
Thanks very much for your comment.
Okay... following up on my earlier comment (and hoping this is the solution you are looking for), you probably want to utilize:
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:]
for each of the cookies for your site.
If you want your changes to the NSHTTPCookieStorage to be retained, you'll also want to call off to
[[NSUserDefaults standardUserDefaults] synchronize];
To prevent this from slowing down your app, you may also want to call this on a background thread like so:
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(backgroundQueue, ^{
//TODO: Cookie deletion logic here
});
EDIT:
If you just need to disregard cookies altogether for a given NSURLRequest, you can do so with:
[request setHTTPShouldHandleCookies:NO];
Where request is your instance of NSURLRequest.
As mentioned by #Niralp it isn't possible to delete all cookies by passing nil to deleteCookie: on an instance of NSHTTPStorage. However, since iOS 8 there has been a removeFromDate: method that can be utilised to the same effect.
In Swift 4 this would be:
HTTPCookieStorage.shared.removeCookies(since: Date(timeIntervalSince1970: 0))
That would remove all cookies in the app since the 1970 epoch which is likely suitable for most needs.
I'm using ASIHTTPRequest to download data from the internet. It has a nifty cacheing feature that you can turn on so that it caches the downloaded data. So far so good. But when I upload new data to my webserver and try to download it again I'm expecting it not to use the cache since the data is new and modified. But even so it will still use the cache.
I'm using the following code for my request:
[ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
// When you turn shouldRespectCacheControlHeaders off, the cache will store responses even if the server
// has explictly asked for them not be be cached (eg with a cache-control or pragma: no-cache header)
[[ASIDownloadCache sharedCache] setShouldRespectCacheControlHeaders:NO];
NSURL *officesUrl = [NSURL URLWithString:#"http://www.example.com/example.json"];
ASIHTTPRequest *officesRequest = [ASIHTTPRequest requestWithURL:officesUrl];
[officesRequest setDefaultResponseEncoding:NSUTF8StringEncoding];
[officesRequest addRequestHeader:#"Cache-Control" value:#"no-cache"];
// Always ask the server if there is new content available,
// If the request fails, use data from the cache even if it should have expired.
[officesRequest setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
[officesRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[officesRequest setDelegate:self];
[officesRequest startAsynchronous];
EDIT:
Two images to show the different headers from HTTPScoop. First image is a fresh download with no cached content. Second image I have uploaded new and modified data.
First:
Second:
EDIT2: Added logs
https://gist.github.com/1139351
I was expecting way more debug output than that, I'm not sure why you got so little.
I think you are just going to have to step through the response processing and see why it decides the cached version is okay to use. The code's pretty easy to follow.
Try setting a breakpoint on useDataFromCache in ASIHTTPRequest.m to start with and canUseCachedDataForRequest, particularly when called from readResponseHeaders.