Why does WKHTTPCookieStore's API -- getAllCookies called the completionHandler but have no session cookies contains - session-cookies

WKNavigationResponse is no longer providing access to cookies through its response.header in iOS 12,
So,I have to user the new API to get all cookies. when I use the getAllCookies:,sometimes it didn't call the completionHandler,sometimes it called the completionHandler without session cookies contain.
under certain circumstances,I need to get the session cookies.
Why does WKHTTPCookieStore's API -- getAllCookies can't always call the completionHandler ?And,why sometimes it called the completionHandler but have no session cookies contain?
Or,Does that have any way to get all cookies contains session cookies from WKWebView?
WKHTTPCookieStore *cookieStore = webView.configuration.websiteDataStore.httpCookieStore;
[cookieStore getAllCookies:^(NSArray* cookies) {
// case 1: didn't call this completionHandler
for (NSHTTPCookie *cookie in cookies) {
//case 2 called this completionHandler without session cookie contains
}
}];

Related

Missing some cookies on NSURLRequest after changing to WKWebView

I am in the process of replacing UIWebView in our app with WKWebView and I have run into a cookie problem. There is a login process in our app where a webform is shown in a web view, and if the login was successful we call a service that gives us our auth token. The service recognises a successful login by a session cookie that was set in the web view. With UIWebView we didn't need to do anything to pass this cookie to the token request since both UIWebView and NSURLSession use the shared NSHTTPCookieStorage. WKWebView however doesn't use the shared cooke storage, so I copy all cookies from it to the shared storage before calling the token service:
[webView.configuration.websiteDataStore.httpCookieStore getAllCookies:^(NSArray* cookies) {
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
}];
This seem to work if I print out the cookies in the shared storage
for (NSHTTPCookie *cookie in [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies) {
NSLog(#"name: %#\tvalue: %#", cookie.name, cookie.value);
}
I get this:
name: mobile value: 220|220
name: BDJSESSIONID value: 0000YHfKy0d8aGHsOKPZBFznsQt:bdaix570
name: vp value: ourdomain-com
name: ukontrol value: nb8sfm1:1590215000215:
name: ACENBP value: ffffffffc3a01a5945525d5f4f58455e445a4a423660
name: clientsession value: NaN
name: _ga value: GA1.2.2108673536.1590134296
name: _gid value: GA1.2.570415333.1590134296
name: xLocale value: da:DK
name: _gat_UA-56861410-1 value: 1
name: PD_STATEFUL_c57e342c-fccf-4a0c-bb38-de5e09832f2c value: %2Fcardapp.services
name: PD_STATEFUL_00819ac9-1bb8-4375-80ff-00c0285bab51 value: %2Fcardapp.services
This looks exactly the same as when I use a UIWebView.
The BDJSESSIONID cookie is the important one here. If I am missing that, the call to the token service will create a new session, where the user is not logged in.
My problem now is that some of the cookies (including BDJSESSIONID) are missing when I call the token service like this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
NSURLSessionTask *sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
...
}];
[sessionTask resume];
These are the cookies actually sent:
I should note that the code for calling the token service was not changed from when we were using UIWebView.
HTTPCookieStorage documenttion mentions following points:
UIWebView - UIWebView instances within an app inherit the parent app's shared cookie storage.
WKWebView - Each WKWebView instance has its own cookie storage. See the WKHTTPCookieStore class for more information.
So the cookies you are adding to shared NSHTTPCookieStorage, will not be directly available in your WKWebView.
Instead, you should set cookies in your WKWebView's WKHTTPCookieStore:
[[[[webView configuration] websiteDataStore] httpCookieStore] setCookie:cookie completionHandler:^{
NSLog(#"Added cookie: %#", cookie);
}];

Share cookies between Xamarin WKWebView and UIWebView

We know that we can share cookies between WKWebViews as discussed here and also in other discussions.
But is it possible to share cookies between a WKWebView and a UIWebView?
Both Http cookies and other cookies.
Did anyone managed to do that?
Is it possible to share cookies between a WKWebView and a UIWebView?
The problem with WKWebView cookies is that requests made by WKWebView do not automatically carry cookies stored in the NSHTTPCookieStorage container.
Solution:
WKWebView Cookie injection is achieved by copying the contents of NSHTTPCookieStorage into WKHTTPCookieStore before executing WKWebView.loadReques() . The sample code is as follows:
CopyCookieToWebviewWithHandler(() =>
{
NSUrl url = new NSUrl("xxx");
NSUrlRequest request = NSUrlRequest.FromUrl(url);
webView.LoadRequest(request);
});
void CopyCookieToWebviewWithHandler(Action completionHandler)
{
NSHttpCookie[] cookies = NSHttpCookieStorage.SharedStorage.Cookies;
WKHttpCookieStore cookieStore = webView.Configuration.WebsiteDataStore.HttpCookieStore;
if(cookies.Length==0)
{
completionHandler();
return;
}
foreach (NSHttpCookie cookie in cookies)
{
cookieStore.SetCookie(cookie,() =>
{
if(cookies[cookies.Length-1]==cookie)
{
completionHandler();
return;
}
});
}
}
Note:WKHttpCookieStore will only available after iOS 11.0

URLSession to use a custom CookieStorage on iOS?

I need a urlsession that stores cookies in a separate cookieStorage
In the folllowing code the cookieStorage in urlSession is the same as the shares cookieStorage, is it possible to create a separate cookie store
let config = URLSessionConfiguration.default
session = URLSession(configuration: config)
config.httpCookieAcceptPolicy = .always
session.configuration.httpCookieStorage = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: "adfadf")
let task = session.dataTask(with: URL(string: "https://www.google.com")!) { (data, response, error) in
print((response as? HTTPURLResponse)?.allHeaderFields ?? "")
DispatchQueue.main.async {
print(self.session.configuration.httpCookieStorage?.cookies ?? "wtf")
print(HTTPCookieStorage.shared === self.session.configuration.httpCookieStorage)
}
}
task.resume()
Same result if I initialize the cookie store using HTTPCookieStorage()
EDIT
I tried creating a cookie store manually and add cookies to it after the request is completed
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: url)
// cookies is not empty
self.cookieStore.setCookies(cookies, for: url, mainDocumentURL: nil)
print(self.cookieStore.cookies) //result is nil
and at the end I end up with nil as cookies
If you open up the header file for NSHTTPCookieStorage you'll see this documentation (for some reason these details don't appear in the regular documentation).
/*!
#method sharedCookieStorageForGroupContainerIdentifier:
#abstract Get the cookie storage for the container associated with the specified application group identifier
#param identifier The application group identifier
#result A cookie storage with a persistent store in the application group container
#discussion By default, applications and associated app extensions have different data containers, which means
that the sharedHTTPCookieStorage singleton will refer to different persistent cookie stores in an application and
any app extensions that it contains. This method allows clients to create a persistent cookie storage that can be
shared among all applications and extensions with access to the same application group. Subsequent calls to this
method with the same identifier will return the same cookie storage instance.
*/
#available(iOS 9.0, *)
open class func sharedCookieStorage(forGroupContainerIdentifier identifier: String) -> HTTPCookieStorage
In order to have a valid app group you need to add it following the instructions in Adding an App to an App Group.
I'm guessing that since you don't have the app groups added to your entitlements it's defaulting to NSHTTPCookieStorage.shared.
Apparently HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: "groupName") seems to work only on iOS 10+, on iOS 9 it will return nil even if the documentation says #available(iOS 9.0, *)
open class func sharedCookieStorage(forGroupContainerIdentifier identifier: String) -> HTTPCookieStorage
You can use this workaround :
let cookies: HTTPCookieStorage
if #available(iOS 10.0, *) {
cookies = HTTPCookieStorage.sharedCookieStorage(forGroupContainerIdentifier: "groupName")
} else {
cookies = HTTPCookieStorage.shared
}

How can I get data out of NSURLRequest config object

I have an Angular web build inside an iOS app and want to POST requests up to the native layer with some JSON that I can use to build some native functionality. I am using the old UIWebView (because Angular) so am using an NSURLProtocol to intercept the request. This works and I can break at the point that the request comes in. The problem is that I can not see the JSON in the data property at this point because it is not the response. The request is still in the config object but I have no idea how to grab this.
My angular code for creating the post is currently like this:
var newdata = $.param({
json: JSON.stringify({
name: "Lee"
})
});
$http.post(url, newdata)
and in my NSURLProtocol class I am successfully intercepting this POST in this method but the HTTPBody property is nil:
override class func canInitWithRequest(request:NSURLRequest) -> Bool {
if (request.URL!.absoluteString as NSString).containsString("request_media_gallery") {
if(request.HTTPBody != nil){
let data:NSData = request.HTTPBody!
print(data)
}
return true
}
return request.URL?.host == "file"
}
If I debug this in chrome I get a 405 because of CORS but I can see that my request object does not have any data but does have a config object. Here's the console log from Chrome:
By the time a URL request gets down to the protocol layer, IIRC, the URL Loading System sanitizes it in a lot of ways. In particular, if a request has an HTTPBody object associated with it, it basically does this:
req.HTTPBodyStream = [NSInputStream inputStreamWithData:req.HTTPBody];
req.HTTPBody = nil;
As a result, to get the data, you need to read from the HTTPBodyStream, regardless of whether the request was originally created with an NSData object or a body stream.

Alamofire - Modify Request object after creation

Short : Is there any way to modify the backed NSURLRequest (headers, body, or other) of the Request object after it has been created ?
Long : I have a custom Manager with startRequestsImmediately set to false. If my access token is currently being refreshed, all requests are waiting for refresh to finish, then they are resume.
Of course, I need to modify theirs HTTP headers to update access token before resuming them.
I can't just track directly NSURLRequests then recreating Requests object because I need to keep all completion closure previously set for those requests.
Is anyone find a way to do that ?
Thanks for your help.
You can use a RequestAdapter. Example:
class MyAdapter: RequestAdapter {
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
urlRequest.setValue("Bar", forHTTPHeaderField: "X-Foo")
return urlRequest
}
}
// Then...
let manager = SessionManager()
manager.adapter = MyAdapter()
manager.request("https://httpbin.org/get")

Resources