Setting cookies with WKHTTPCookieStorage do not sync with wkwebview

I created a method to set cookies from HTTPCookieStorage into WKHTTPCookieStorage, as it seems this api is full async so i created an async method that sets the cookies in wkHTTPCookieStorage.


func copyCookiesToWKStore(completion: @escaping () -> Void) {
        if let httpCookies: [HTTPCookie] = self.httpCookieStorage.cookies {
            print("updating wkCookies with httpCookies: \(httpCookies)")
            func copyCookiesAsync(index: Int, cookieHandler: SDCookiesHandler?) {
                if let cookieHandler = cookieHandler, index < httpCookies.count {
                    let cookie = httpCookies[index]
                    cookieHandler.wkCookieStore.setCookie(cookie, completionHandler: {
                        print("set cookie in Cookie Storage: \(cookie)")
                        DispatchQueue.main.async {
                            copyCookiesAsync(index: index + 1, cookieHandler: cookieHandler)
                        }
                    })
                }
                else {
                    completion()
                }
            }
            weak var cookieHandler = self
            copyCookiesAsync(index: 0, cookieHandler: cookieHandler)
        } else {
            completion()
        }
    }



and the load the request in the wkwebview:


wkCookiesHandler.copyCookiesToWKStore { [weak self] in
    if let weakSelf = self {
       DispatchQueue.main.async {
          weakSelf.webView.load(cookiesRequest)
       }
    }



However when webview calls the decidePolicyFor navigationAction method, the cookies in WKCookieStorage are not fully synced at this point.

I have read in some posts that WKProcessPool has its own cycle to sync cookies, and that resetting it, causes cookies to sync with wkwebview, but i found that i am in existent webview flow, it causes weird artifacts with the webview.

Has anyone started using WKCookieStorage api, and how can one keep cookies inserted in this api, visible by the WKWebview.

Hi,


I have a similar problem. I used to sync HTTPCookieStorage with WKHTTPCookieStore with the following method.

@available(iOS 11.0, *)
    func fillCookieStore(store: WKHTTPCookieStore, completionHandler: (() -> Void)? = nil) {
       
        DDLogDebug("HttpCookieStorage: \(String(describing: HTTPCookieStorage.shared.cookies))")
       
        if let cookies = HTTPCookieStorage.shared.cookies {
           
            let sem = DispatchGroup()
            for cookie in cookies {
                sem.enter()
                store.setCookie(cookie, completionHandler: {
                    sem.leave()
                })
            }
           
            sem.notify(queue: .main) {
                completionHandler?()
            }
        } else {
            completionHandler?()
        }
    }


This used to work just fine until iOS 11.3 Beta. Since then my cookies are not available anymore. I have no clue what to do.


Best regards

Peter

Hi Peter,


Does your code work well with iOS versions before 11.3, (11.3 is still in beta so you might have a solution). Do you insert your cookies before setting the configuration into webview, or does this work even if the configuration is set?

Most of our cases scenarios require us to set the cookies to an existent webview and a share WKProcessPool.



Regards

Hi,

it work's well for all version greater 11.0 and before 11.3. I insert the cookies AFTER setting the configuration to webview. Out of curiosity I tried to insert them before. But that doesnt work.


Best regards

Peter

This is rather strange, just out of curiosity how many webviews instances do you have running and also are your WKProcessPools shared or isolated.

I found that when i set the cookies before i call load(request) in the wkwebview, the cookies werent updated when decided decidePolicyFor navigationAction: WKNavigationAction is called.


At this point the webview is part of the its superview, the configuration is set, and it is using a shared WKProcessPool, i found if i create at this point a new process pool the the cookies are inserted, but sometimes pages dont load correctly...


When do you set the cookies, before or after adding it to subview?


Apologies for picking your brain, just trying to understand the differences between ur approach and mine, when i finished adding my cookies to WKCookiestorage, if get all cookies i could see them there, but not when i called them at the decidePolicyFor navigationAction: WKNavigationAction method, which is rather strange.


To me it seems its all related to the WKProcessPool and it update schedule, but maybe i am wrong.

I'm having an identical issue. I have seent the same cookie behavior as described above. Cookies report as being in the WHHTTPCookieStore and I wait for the callbacks to finish, but the WKWebView (process pool?) doesn't seem to have them 100% of the time when the request is made.

I see random success, indicated a race condition, or in other words, likely a process pool cookie sync scheduling issue

We have apps in production with this problem since iOS 11.3 was released causing users to give us 1 stars ratings. Apple must find a solution to this problem ASAP. Our code is really not different than the others solutions I see here.

Same problem for us. Our approach is very similar to the two posted here. Everything worked fine on every device prior to 11.3, but it fails on every device running 11.3 and above.

Below are some code snippets from our implementation. Again, all of this worked fine prior to iOS 11.3. I tried adding an observer to the WKHTTPCookieStore associated with the WKWebView instance, but "cookiesDidChange" never seems to be hit.


   
     // Create the WKWebView instance. Add a cookie store observer.
    if #available(iOS 11, *){
        wkWebView = WKWebView()
        wkWebView!.scrollView.delegate = self
        wkWebView!.configuration.websiteDataStore.httpCookieStore.add(OnShiftWKHTTPCookieStoreObserver())
    }


    // this is how we're adding cookies to our WKWebView instance.
    public func writeCookies(cookies: [[HTTPCookiePropertyKey : Any]]) {
        if #available(iOS 11, *) {
            let cookiesStore = wkWebView.configuration.websiteDataStore.httpCookieStore
            cookies.forEach { (cookie) in
                guard let cookie = HTTPCookie.init(properties: cookie) else {
                    return
                }
                cookiesStore.setCookie(cookie, completionHandler: {
                    print ("cookie added: \(cookie.name)")
                })
            }
        }
    }


// This is our WKHTTPCookieStoreObserver implementation. Note that "cookiesDidChange" isn't called
// even after several calls to setCookie above.
class OnShiftWKHTTPCookieStoreObserver: NSObject, WKHTTPCookieStoreObserver {
    @available(iOS 11.0, *)
    func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
        cookieStore.getAllCookies({(cookies: [HTTPCookie]) in
            cookies.forEach({(cookie: HTTPCookie) in
                print("COOKIE name: \(cookie.name) domain: \(cookie.domain) value: \(cookie.value)")
            })
        })
    }
}

I spent some time on this issue as well. I managed to get my app working again, with the following two alterations:


1. Before creating the WKWebView, observe the default WKWebsiteDataStore instance for cookie changes.


// These two lines occur in the viewDidLoad method of a UIViewController class
// This view controller conforms to the WKHTTPCookieStoreObserver protocol
WKWebsiteDataStore.default().httpCookieStore.add(self)
let webView = WKWebView()
// Configure and load the web view


2. In the cookiesDidChange(in:) method of your WKHTTPCookieStoreObserver, retrieve the cookies from the WKHTTPCookieStore asynchronously, on the main thread.


func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
    DispatchQueue.main.async {
          cookieStore.getAllCookies { cookies in
              // Process cookies
          }
    }
}


Now my app runs successfully on iOS 11.3 and 11.3.1.

Thank you! This appears to fix our problem as well.

I finally made this work again. My error was kind of trivial 🙂 I found a working solution in last years wwdc video around minute 18. https://developer.apple.com/videos/play/wwdc2017/220/


When I compared this to my code, I saw that I was using my WKWebViewConfiguration's WKWebsiteDataStore without setting it before usage. This used to work. But it looks like it doesnt anymore. So all I endet up doing was this:

let store = WKWebsiteDataStore.nonPersistent()
webConfiguration.websiteDataStore = store

I found this solution as well by looking at the WWDC session. However, I shortly thereafter discovered that using a non-persistent WKWebsiteDataStore had other problems. The website I'm integrating was using Web Storage. It appears that if using a non-persistent store, data stored in Window.sessionStorage or Window.localStorage will not be retained over a page refresh. Thus I'm back to square one.


If anyone has any idea on how to reliably set cookies even on the default store, I'd be happy to hear.

Hi! I'd like to just ask for a clarification here – what problem does this solution solve?


Does observing the cookie store and getting all the cookies on the main thread actually help with the problem of _setting_ cookies?

*bump
Having a similar issue where not all cookies are passed to ajax (and following requests). Any idea on how to set cookies for all calls inside the WKWebview ?

no one mentioned how to sync cookies less than ios version 11 using wkwebview.

its been long time , apple did not introduce cookie fix for ios less than 11. if there is no solution for cookie sync for less than ios 11 then why Apple mentinoned in documentation Wkwebview available from iOS 8 😟 :@

@AlexOak a thousand thank yous for posting this solution. It's crazy that this is necessary, but it works. Did you file a bug report with Apple for this? I've spent untold hours trying to figure out why cookies are sometimes set and sometimes not, with different results pretty much every time I ran the app. Just adding the observer fixes the problem completely.

The problem with syncing cookies to WKWebView lies in WKProcessPool. To properly sync cookie, you have to create an instance of WKProcessPool and set it to the WKWebViewConfiguration that is to be used to initialize the WkWebview itself:


    private lazy var mainWebView: WKWebView = {
        let webConfiguration = WKWebViewConfiguration()
        if Enviroment.shared.processPool == nil {
            Enviroment.shared.processPool = WKProcessPool()
        }
        webConfiguration.processPool = Enviroment.shared.processPool!
        webConfiguration.processPool = WKProcessPool()
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = self
        return webView
    }()


Setting WKProcessPool is the most important step here. WKWebview makes use of process isolation - which means it runs on a different process than the process of your app. This can sometimes cause conflict and prevent your cookie from being synced properly with the WKWebview. If you don't use the same instance of WKProcessPool each time you configure a WKWebView for the same domain (maybe you have a VC A that contains a WKWebView and you want to create different instances of VC A in different places), there can be conflict setting cookies. To solve the problem, after the first creation of the WKProcessPool for a WKWebView that loads domain B, I save it in a singleton and use that same WKProcessPool every time I have to create a WKWebView that loads the same domain B


After the initialization process, you can load an URLRequest inside the completion block of httpCookieStore.setCookie. Here, you have to attach the cookie to the request header otherwise it won't work.


    mainWebView.configuration.websiteDataStore.httpCookieStore.setCookie(your_cookie) {
            self.mainWebView.load(your_request, with: [your_cookie])
    }


    extension WKWebView {
       func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
          var request = request
          let headers = HTTPCookie.requestHeaderFields(with: cookies)
          for (name, value) in headers {
             request.addValue(value, forHTTPHeaderField: name)
          }      
          load(request)
       }
    }

You are not actually using the singleton in the code above. Well you are but then you immediately overwrite it with a fresh WKProcessPool() on line 7.

I have a similar problem. I used to sync HTTPCookieStorage with WKHTTPCookieStore but my cookies not saved and every time it return logout user.


Setting cookies with WKHTTPCookieStorage do not sync with wkwebview
 
 
Q