Cookies set in a redirect response are not persisted - session-cookies

This appears to be a related issue only on iOS: since updating React Native from 0.55 to 0.57.
adding credentials: 'include' helps, but this flag does not help if you reopen the app. Cookie will be deleted after app relaunch.
Below link actually proposes a PR release around this, but even that is not resolving the error.
https://github.com/facebook/react-native/commit/a6860487947ae0957f5dfa4979e92bc7740fecb0
This is addition to the file react-native/Libraries/Network/RCTHTTPRequestHandler.mm
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
// Add the cookies to the new request
// This is necessary because we're not letting iOS handle cookies by itself
NSMutableURLRequest *nextRequest = [request mutableCopy];
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
nextRequest.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
completionHandler(nextRequest);
}

Related

How to get cookies from WKWebView

I have this WKWebView which loads a login page and what I need is the "iPlanetDirectoryPro" cookie (see picture bellow) that is set after a successful login (form submit). So, I'm trying to store it in order to use it in another WKWebView.. The funny thing is "sharedHTTPCookieStorage" contains the other cookies but not the "iPlanetDirectoryPro" one.
What I tried so far:
Created a shared proccess pool and used the same configuration for this first WKWebView and the one I'm trying to use "iPlanetDirectoryPro" on.
I used this delegate method decidePolicyForNavigationResponse to fetch cookies:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
Evaluating JavaScript command document.cookie on webView.
Any ideas?
For whatever reason, WKWebView and HTTPCookieStorage aren't working perfectly together. You would need to manage the cookies yourself, for example you could look here

How to get cookie from WKwebview (IOS)

I'd like to receive a cookie containing login status through WKwebview.
what I finally want is receiving that cookie data, parsing them,and then changing the view for the user logged in.
What I've tired :
webview.evaluateJavascript("document.cookie.search('LoginSession=Y')") { (data,error) -> .....
}
result : if the data is 'data >= 1', login status(a variable in IOS app) = true, but under 0(data < 0),login status will be false.
and it works like a charm seemingly for my app.
However, This way looks very a physical and simple way, so I think, it could be not secure for some users, and It might have no guarantee for working perfectly for all environments with IOS.
Q1 : Is it not dangerous way?
Q2 : I've heard that IOS stores the cookies in Memory unlike other platforms, and we could manage to load the cookie data from Memory through some codes. Are there any recommended libraries for developers to handle cookies from WKWEB?
I tried this. Javascript returns a single string with all cookies in form "key=value;"
I don't know how stable it is. Hope it helps.
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
[webView evaluateJavaScript:#"document.cookie;" completionHandler:^(NSString *result, NSError *error)
{
NSLog(#"Error getting cookies: %#",error);
[self updateCookies:result];
}];
}
Courtesy : Siyu Song's Answer
You can have access to an NSHTTPURLResponse object in - webView:decidePolicyForNavigationResponse:decisionHandler: method defined on WKNavigationDelegate. You can later extract the cookies manually from the HTTP header:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:[NSURL URLWithString:#""]];
for (NSHTTPCookie *cookie in newCookies) {
// Do something with the cookie
}
decisionHandler(WKNavigationResponsePolicyAllow);
}
Please post your solution if you have a better one.

AFHTTPSessionManager with Cache Policy not working

Server sends cache header in response.
"Cache-Control": "max-age=120, public"
First I used NSURLRequestReturnCacheDataElseLoad with the AFHTTPRequestOperation.
By setting shared cache in AppDelegate and setting the NSURLRequestReturnCacheDataElseLoad in the NSUrlRequest.
It worked fine.
But when i tried to do the same with AFHTTPSessionManager by setting NSURLRequestReturnCacheDataElseLoad in the following ways,
1.request.session.configuration.requestCachePolicy
2.request.requestSerializer.cachePolicy
3.Overriding -(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler
Nothing seems to be working.
AFNetworking version - 2.5.1
I had the same problem and after hours of searching, I have found that AFHTTPSessionManager uses cookies to cache requests.
Simply deleted all cookies and it all worked.
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}

NSURLSessionDownloadTask not deleting the file when the app is closed by the user and the task was still active

I have a NSURLSession and a NSURLSessionDownloadTask configured for downloading a file in background, if the download task in canceled by the user all the data is deleted and the storage space the file was using is freed, but if the app is closed from the multitasking dock the download task is terminated and gives an error but is not deleting the data and the temporal data for the file is still occupying storage space and is never freed. What do i need to do in order to free the space ?
This is my NSURLSession configuration and error handling:
- (NSURLSession *)backgroundSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration;
if ([[UIDevice currentDevice].systemVersion hasPrefix:#"7"]) configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.visyon.pr"];
else configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"com.visyon.pr"];
configuration.sessionSendsLaunchEvents =YES;
session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
});
return session; }
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error == nil) {
NSLog(#"Task: %# completed successfully", task );
} else {
// [self hideActivity];
// [self showAlertBoxErrorDownload];
NSLog(#"Task: %# completed with error: %#, %lu", task, [error localizedDescription], (long)error.code);
} self.downloadTask = nil; }
ok after more than 10 days of try and fail I found the solution, first of all there are two case scenarios when the user close the app and there is an active download in background, please take in to account that for this cases the download task is never intended to be resumed.
scenario 1. when the user close the app but is still active, the - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error is called and the temporal file that was using for the download goes directly to the temp directory of the app, in this case the - (void)applicationWillTerminate:(UIApplication *)application is called but the delegate of the download task is called first. The solution is this case is to implement code to clean the temp directory every time the app is opened or let iOS to clean the temp folder, it is explained here when iOS is going to clean the temp file:
When does iOS clean the local app ./tmp directories?
scenario 2. when the user close the app in background mode the app is terminated and the next time the app is opened the - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error is called and the temporal file that was using for the download is stored in the NSCachesDirectory in the next directory:
var/mobile/Containers/Data/Application/CA21-B6E8-3305A39/Library/Caches/com.apple.nsurlsessiond/Downloads/com.xxx.xxx/CFNetworkDownload_M5o8Su.tmp
The temporal file will be move to the temporal directory of the app the next time there is a new download.
the solution here is to implement code to delete all temporal files from Caches/com.apple.nsurlsessiond/Downloads/com.xxx.xxx/ as soon as the - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error is launched.
Here is the code in order to delete the temporal files from NSCachesDirectory:
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[path stringByAppendingPathComponent:#"/com.apple.nsurlsessiond/Downloads/com.xxx.xxx/"] error:nil];
for (NSString *string in array) {
[[NSFileManager defaultManager] removeItemAtPath:[path stringByAppendingPathComponent:[NSString stringWithFormat:#"/com.apple.nsurlsessiond/Downloads/com.xxx.xxx/%#", string]] error:nil];
}
But because it only work for this scenario its better to implement code to clean the temporal directory of the app every time is launched or let iOS to clean the temp folder so it will work for both scenarios.
Here is the code on how to clean the temporal directory of the app:
NSString *path = NSTemporaryDirectory();
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
for (NSString *string in array) {
[[NSFileManager defaultManager] removeItemAtPath:[path stringByAppendingPathComponent:string] error:nil];
}
First point is if you are configure nsurlsession with background configuration type. Then this type of session till runing if user kill the app.
Second point if you want to clear space then you need to stop that session and manually write code for deleting temporary file from tmp folder.
Change session configuration type.
There's a method in the AppDelegate called - (void)applicationWillTerminate:(UIApplication *)application. You can try either calling [NSURLSessionDownloadTask cancel]; from there; or try setting a flag to indicate to the app there was a download in progress when the app was closed and delete the data the next time the app is opened.

iOS/Cocoa - NSURLSession - Handling Basic HTTPS Authorization

[edited to provide more information]
(I'm not using AFNetworking for this project. I may do so in future, but wish to resolve this problem/misunderstanding first.)
SERVER SETUP
I cannot provide the real service here, but it is a simple, reliable service that returns XML according to a URL such as:
https://username:password#example.com/webservice
I want to connect to the URL over HTTPS using GET, and determine any authentication failures (http status code 401).
I have confirmed that the web service is available, and that I can successfully (http status code 200) grab XML from the url using a specified username and password. I have done this with a web browser, and with AFNetworking 2.0.3, and by using NSURLConnection.
I have also confirmed that I am using the correct credentials at all stages.
Given the correct credentials and the the following code:
// Note: NO delegate provided here.
self.sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfig
delegate:nil
delegateQueue:nil];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:self.requestURL completionHandler: ...
The above code will work. It will successfully connect to the server, get a http status code of 200, and return the (XML) data.
PROBLEM 1
This simple approach fails in cases where the credentials are invalid. In that case, the completion block is never called, no status code (401) is provided, and eventually, the Task times out.
ATTEMPTED SOLUTION
I assigned a delegate to the NSURLSession, and am handling the following callbacks:
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
if (_sessionFailureCount == 0) {
NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
_sessionFailureCount++;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
if (_taskFailureCount == 0) {
NSURLCredential *cred = [NSURLCredential credentialWithUser:self.userName password:self.password persistence:NSURLCredentialPersistenceNone];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
_taskFailureCount++;
}
PROBLEM 1 WHEN USING ATTEMPTED SOLUTION
Please note the use of ivars _sessionFailureCount and _taskFailureCount. I am using these because the challenge object's #previousFailureCount property is never advanced! It always remains at zero, no matter how many times these callback methods are called.
PROBLEM 2 WHEN USING ATTEMPTED SOLUTION
Despite the use of correct credentials (as proven by their successful use with a nil delegate), authentication is failing.
The following callbacks occur:
URLSession:didReceiveChallenge:completionHandler:
(challenge # previousFailureCount reports as zero)
(_sessionFailureCount reports as zero)
(completion handler is called with correct credentials)
(there is no challenge #error provided)
(there is no challenge #failureResponse provided)
URLSession:didReceiveChallenge:completionHandler:
(challenge # previousFailureCount reports as **zero**!!)
(_sessionFailureCount reports as one)
(completion handler is called with request to cancel challenge)
(there is no challenge #error provided)
(there is no challenge #failureResponse provided)
// Finally, the Data Task's completion handler is then called on us.
(the http status code is reported as zero)
(the NSError is reported as NSURLErrorDomain Code=-999 "cancelled")
(The NSError also provides a NSErrorFailingURLKey, which shows me that the URL and credentials are correct.)
Any suggestions welcome!
You don't need to implement a delegate method for this, simply set the authorization HTTP header on the request, e.g.
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://whatever.com"]];
NSString *authStr = #"username:password";
NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *authValue = [NSString stringWithFormat: #"Basic %#",[authData base64EncodedStringWithOptions:0]];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
//create the task
NSURLSessionDataTask* task = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
Prompted vs Unprompted HTTP Authentication
It seems to me that all documentation on NSURLSession and HTTP Authentication skips over the fact that the requirement for authentication can be prompted (as is the case when using an .htpassword file) or unprompted (as is the usual case when dealing with a REST service).
For the prompted case, the correct strategy is to implement the delegate method:
URLSession:task:didReceiveChallenge:completionHandler:; for the unprompted case, implementation of the delegate method will only provide you with the opportunity to verify the SSL challenge (e.g. the protection space). Therefore, when dealing with REST, you will likely need to add Authentication headers manually as #malhal pointed out.
Here is a more detailed solution that skips the creation of an NSURLRequest.
//
// REST and unprompted HTTP Basic Authentication
//
// 1 - define credentials as a string with format:
// "username:password"
//
NSString *username = #"USERID";
NSString *password = #"SECRET";
NSString *authString = [NSString stringWithFormat:#"%#:%#",
username,
secret];
// 2 - convert authString to an NSData instance
NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding];
// 3 - build the header string with base64 encoded data
NSString *authHeader = [NSString stringWithFormat: #"Basic %#",
[authData base64EncodedStringWithOptions:0]];
// 4 - create an NSURLSessionConfiguration instance
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
// 5 - add custom headers, including the Authorization header
[sessionConfig setHTTPAdditionalHeaders:#{
#"Accept": #"application/json",
#"Authorization": authHeader
}
];
// 6 - create an NSURLSession instance
NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig delegate:self
delegateQueue:nil];
// 7 - create an NSURLSessionDataTask instance
NSString *urlString = #"https://API.DOMAIN.COM/v1/locations";
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:
^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
if (error)
{
// do something with the error
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
// success: do something with returned data
} else {
// failure: do something else on failure
NSLog(#"httpResponse code: %#", [NSString stringWithFormat:#"%ld", (unsigned long)httpResponse.statusCode]);
NSLog(#"httpResponse head: %#", httpResponse.allHeaderFields);
return;
}
}];
// 8 - resume the task
[task resume];
Hopefully this will help anyone that runs into this poorly documented difference. I finally figured it out using test code, a local proxy ProxyApp and forcibly disabling NSAppTransportSecurity in my project's Info.plist file (necessary for inspecting SSL traffic via a proxy on iOS 9/OSX 10.11).
Short answer: The behavior you describe is consistent with a basic server authentication failure. I know you've reported that you've verified that it's correct, but I suspect some fundamental validation problem on the server (not your iOS code).
Long answer:
If you use NSURLSession without the delegate and include the userid/password in the URL, then completionHandler block of the NSURLSessionDataTask will be called if the userid/password combination is correct. But, if the authentication fails, NSURLSession appears to repeatedly attempt to make the request, using the same authentication credentials every time, and the completionHandler doesn't appear to get called. (I noticed that by watching the connection with Charles Proxy).
This doesn't strike me as very prudent of NSURLSession, but then again the delegate-less rendition can't really do much more than that. When using authentication, using the delegate-based approach seems more robust.
If you use the NSURLSession with the delegate specified (and no completionHandler parameter when you create the data task), you can examine the nature of the error in didReceiveChallenge, namely examine the challenge.error and the challenge.failureResponse objects. You might want to update your question with those results.
As an aside, you appear to be maintaining your own _failureCount counter, but you can probably avail yourself of challenge.previousFailureCount property, instead.
Perhaps you can share some particulars about the nature of the authentication your server is using. I only ask, because when I secure a directory on my web server, it does not call the NSURLSessionDelegate method:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
But rather, it calls the NSURLSessionTaskDelegate method:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
Like I said, the behavior you describe is consist with an authentication failure on the server. Sharing the details about the nature of the authentication setting on your server and the particulars of the NSURLAuthenticationChallenge object might help us diagnose what's going on. You might also want to type the URL with the userid/password in a web browser and that might also confirm whether there is a basic authentication problem.

Resources