Getting all cookies from WKWebView
Asked Answered
G

14

82

while getting cookies from UIWebView seems straightforward by using NSHTTPCookieStorage.sharedHTTPCookieStorage(), it seems WKWebView stores the cookies somewhere else.

I did some research, and I was able to get some cookies from the grabbing it from NSHTTPURLResponse object. this, however, does not contain all the cookies used by WKWebView:

func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {

  if let httpResponse = navigationResponse.response as? NSHTTPURLResponse {
    if let headers = httpResponse.allHeaderFields as? [String: String], url = httpResponse.URL {
      let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url)

      for cookie in cookies {
        logDebug(cookie.description)

        logDebug("found cookie " + cookie.name + " " + cookie.value)
      }
    }
  }
}

Strangely, there's also a class WKWebsiteDataStore in ios 9 that responsible for managing cookies in WKWebView, however, the class does not contain a public method to retrieve the cookies data:

let storage = WKWebsiteDataStore.defaultDataStore()

storage.fetchDataRecordsOfTypes([WKWebsiteDataTypeCookies], completionHandler: { (records) -> Void in
  for record in records {
    logDebug("cookie record is " + record.debugDescription)

    for dataType in record.dataTypes {
      logDebug("data type is " + dataType.debugDescription)

      // get cookie data??
    }
  }
})

Is there a workaround for getting the cookie data?

Gittel answered 15/10, 2015 at 19:9 Comment(4)
Worth noting that the WebKit team seems to be working on a proper way to access WKWebView's cookie storage: bugs.webkit.org/show_bug.cgi?id=140191Idealize
@Gittel have you found any solution yet, i am working on this from months but didn't get any solution yet :(Thomism
#39772507Functionary
@Gittel you have not mention get cookie data :)Jacqulynjactation
D
31

Finally, httpCookieStore for WKWebsiteDataStore landed in iOS 11.

https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor

Dominus answered 26/6, 2017 at 14:44 Comment(1)
What about for iOS 10 or belowPilotage
K
76

Cookies used (created) by the WKWebView are actually correctly stored in the NSHTTPCookieStorage.sharedHTTPCookieStorage().

The problem is that the WKWebView does not write back the cookies immediately. I think it does this on its own schedule. For example when a WKWebView is closed or maybe periodically.

So eventually they do end up in there, but when is unpredictable.

You may be able to force a 'sync' to the shared NSHTTPCookieStorage by closing your WKWebView. Please let us know if this works.

Update: I just remembered that in Firefox for iOS we force the WKWebView to flush its internal data, including cookies, by replacing its WKProcessPool with a new one. There is no official API, but I am pretty sure that is the most reliable workaround right now.

Kenji answered 18/10, 2015 at 14:1 Comment(6)
thanks. I'll check it out. Do you have a reference to a function that perform that WKProcessPool switch? Do I just replace the pool with a new one?Gittel
Anyone experience with the workaround described in this answer?Demitria
What do you mean by 'Close the WKWebView'? removeFromSuperview and set it to nil?Ruttger
i need help for WKWebView cookies.Meave
For those looking at this and confused, I literally did: self.webView.configuration.processPool = [[WKProcessPool alloc] init]; and it worked to flush the cookies so they were available inNSHTTPCookieStorage.sharedHTTPCookieStorage(), however it only works on device for me, not simulator.Capablanca
Is this answer up to date?Goodsized
D
31

Finally, httpCookieStore for WKWebsiteDataStore landed in iOS 11.

https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor

Dominus answered 26/6, 2017 at 14:44 Comment(1)
What about for iOS 10 or belowPilotage
T
31

Details

  • Xcode 9.2, Swift 4
  • Xcode 10.2 (10E125), Swift 5

Solution

extension WKWebView {

    private var httpCookieStore: WKHTTPCookieStore  { return WKWebsiteDataStore.default().httpCookieStore }

    func getCookies(for domain: String? = nil, completion: @escaping ([String : Any])->())  {
        var cookieDict = [String : AnyObject]()
        httpCookieStore.getAllCookies { cookies in
            for cookie in cookies {
                if let domain = domain {
                    if cookie.domain.contains(domain) {
                        cookieDict[cookie.name] = cookie.properties as AnyObject?
                    }
                } else {
                    cookieDict[cookie.name] = cookie.properties as AnyObject?
                }
            }
            completion(cookieDict)
        }
    }
}

Usage

// get cookies for domain
webView.getCookies(for: url.host) { data in
      print("=========================================")
      print("\(url.absoluteString)")
      print(data)
}

// get all cookies
webView.getCookies() { data in
      print("=========================================")
      print("\(url.absoluteString)")
      print(data)
}

Full sample

Info.plist

add in your Info.plist transport security setting

 <key>NSAppTransportSecurity</key>
 <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
 </dict>

Code

  1. Do not forget to add the solution code here
  2. ViewController has embed view controller
import UIKit
import WebKit

class ViewController: UIViewController {

    private lazy var url = URL(string: "https://google.com")!
    private weak var webView: WKWebView?

    func initWebView(configuration: WKWebViewConfiguration) {
        if webView != nil { return }
        let webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
        webView.navigationDelegate = self
        webView.uiDelegate = self
        view.addSubview(webView)
        self.webView = webView
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if webView == nil { initWebView(configuration: WKWebViewConfiguration()) }
        webView?.load(url: url)
    }
}

extension ViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        decisionHandler(.allow)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        if let url = webView.url {
            webView.getCookies(for: url.host) { data in
                print("=========================================")
                print("\(url.absoluteString)")
                print(data)
            }
        }
    }
}

extension ViewController: WKUIDelegate {

    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        // push new screen to the navigation controller when need to open url in another "tab"
        if let url = navigationAction.request.url, navigationAction.targetFrame == nil {
            let viewController = ViewController()
            viewController.initWebView(configuration: configuration)
            viewController.url = url
            DispatchQueue.main.async { [weak self] in
                self?.navigationController?.pushViewController(viewController, animated: true)
            }
            return viewController.webView
        }
        return nil
    }
}

extension WKWebView {

    func load(urlString: String) {
        if let url = URL(string: urlString) { load(url: url) }
    }

    func load(url: URL) { load(URLRequest(url: url)) }
}

enter image description here

Theoretical answered 15/12, 2017 at 8:22 Comment(2)
can I get the complete project?Amphiboly
@ShahidGhafoor sure, dropbox.com/s/kft7ue4zgn4p5hl/stackoverflow-33156567.zip?dl=0Theoretical
Z
16

I know this is a very old question, and we have a solution but work only on iOS 11 and upper. For those one who are dealing with iOS 10 and lower (like me), you may consider this method. It works perfectly to me:

  • Force reset processPool:
extension WKWebView {
    func refreshCookies() {
        self.configuration.processPool = WKProcessPool()
        // TO DO: Save your cookies,...
    }
}

--> this only work on real device.

  • For simulator, you should add:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let response = navigationResponse.response as? HTTPURLResponse,
       let allHttpHeaders = response.allHeaderFields as? [String: String],
       let responseUrl = response.url {
        let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHttpHeaders, for: responseUrl)

        for cookie in cookies {
            HTTPCookieStorage.shared.setCookie(cookie)
        }
    }

    decisionHandler(.allow)
}

Follow to the answer of Stefan Arentz and Phenom.

Zumwalt answered 10/4, 2018 at 3:10 Comment(4)
Did you forget how actually copy the cookies?Goodsized
When is the refreshCookies() called? I guess before we need it to retrieve the cookies? It is still not working for the website I am using, tested on actual device running iOS 13.1.2. Is it because of session/persistent cookies?Effects
I'm back and voting this up, to get the cookies I added self.configuration.websiteDataStore.httpCookieStore.getAllCookies { (cookies) in /* your own code */}. Only tested on device so far (iOS 13).Goodsized
always i get empty "cookies" array. Please help mePilotage
S
10

For iOS 11, without any extensions:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        for cookie in cookies {
            //...
        }
    }
}
Schnorr answered 19/8, 2019 at 19:52 Comment(4)
I am an Android developer, completely new to IOS, just need to solve this issue! So my question is when do you call getAllCookies? After the webview.loadUrl? Or before? Please give me some pointers thank you!Vesper
It needs to be called when a webpage is fully loaded in Webview.Schnorr
much appreciated! I actually solved the problem, it was bit hard for me at the beginning.Vesper
Or in Objective-C: webView.configuration.processPool = [[WKProcessPool alloc] init]; [[WKWebsiteDataStore defaultDataStore].httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * cookies) { //DO YOUR STUFF WITH THE 'cookies' VARIABLE }Legitimist
B
5
if (@available(iOS 11.0, *)) {
  [webView.configuration.websiteDataStore.httpCookieStore
      getAllCookies:^(NSArray<NSHTTPCookie *> *_Nonnull cookies) {
        NSURLRequest *request =
            [[NSURLRequest alloc] initWithURL:self.URL]; //your URL
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *task = [session
            dataTaskWithRequest:request
              completionHandler:^(NSData *responseData, NSURLResponse *response,
                                  NSError *error) {
                //Do Something
              }];
        [task resume];
        [session.configuration.HTTPCookieStorage storeCookies:cookies forTask:task];
      }];
}
Buttonwood answered 20/8, 2018 at 11:33 Comment(1)
Thanks your answer is help to me, but it's work iOS 11.0 or Above. I want to do same execution for iOS 10 or below. Please help me. Means i need to get all cookies in iOS 10.Pilotage
I
4

I used WKHTTPCookieStore in Objective-C, This worked for me to get both persistent and session cookies, but it only works in iOS 11+

https://developer.apple.com/documentation/webkit/wkhttpcookiestore?changes=latest_minor&language=objc

 if (@available(iOS 11.0, *)) {
     WKHTTPCookieStore *cookieStore = _webView.configuration.websiteDataStore.httpCookieStore;
     [cookieStore getAllCookies:^(NSArray* cookies) {
        NSHTTPCookie *cookie;
        for(cookie in cookies){
            NSLog(@"cookie: %@", cookie);
        }
 }];

Forcing the WKWebView to flush its internal data by replacing its WKProcessPool as described by Stefan's answer worked for me in iOS 10 and 11 but only for persistent cookies; it seems like session cookies get removed, as J. Thoo described

Isogamy answered 31/7, 2018 at 17:56 Comment(1)
Hi jorge, whats your solution for iOS 13.0 and above?Tarttan
C
4

Swift 5

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        debugPrint(cookies.debugDescription)
    }

    decisionHandler(.allow)
}
Carothers answered 2/8, 2019 at 10:35 Comment(0)
A
2

As Stefan mentioned, cookies are stored in NSHTTPCookieStorage.sharedHTTPCookieStorage()

However, from my experiments, I found that Session cookies set by the server are not visible to NSHTTPCookieStorage.sharedHTTPCookieStorage().

As long as each WKWebView share the same instance of WKProcessPool, those Session cookies will be passed back to the server for each request. If you change the process pool for a WKWebView, you are essentially removing the session cookies for all future requests.

Absquatulate answered 9/12, 2015 at 22:46 Comment(1)
yes, it's right to say that cookie are stored in NSHTTPCookieStorage.sharedHTTPCookieStorage(). However, the issue is: if an user has signed in UIWebView, it does not automatically signed in another WKWebView, vice visa. So my belief is: even though they share the same cookie, the principle behind them is quite differenentGreasepaint
V
1

Don't waste you time in extracting cookies from iOS 11 below device, there are very less chances of getting succeeded. Cookie extraction may get blocked due some security reasons.

Refer these logs:

2019-02-07 00:05:45.548880+0530 MyApp[2278:280725] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C8.1:2][0x10fd776f0] get output frames failed, state 8196

2019-02-07 00:05:45.550915+0530 MyApp[2278:280725] TIC Read Status [8:0x0]: 1:57

Try this code which is build for below iOS 11 devices:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        let cookieValue = HTTPCookieStorage.shared.cookies(for: navigationResponse.response.url!)
        print(cookieValue!)
        let response = navigationResponse.response as! HTTPURLResponse
        let headFields = response.allHeaderFields as! [String:String]

        let cookies = HTTPCookie.cookies(withResponseHeaderFields: headFields, for: response.url!)
        for cookie in cookies {
            print("name: \(cookie.name) value: \(cookie.value)")
        }
        decisionHandler(.allow)
    }

The above code will give you empty cookie array, as cookies extraction are being blocked due to some security reasons.

I would recommend you to try following which is meant for iOS 11 and above:

WKWebsiteDataStore.default().httpCookieStore.getAllCookies { (cookies) in
    for cookie in cookies {
        print(cookie)
    }
}
Verwoerd answered 7/2, 2019 at 12:50 Comment(0)
G
0

In practice, I found in the method of "decidePolicyForNavigationResponse", you can use following way to fetch cookies, but the sad thing is it's not a complete/whole list for a session.

let response = navigationResponse.response as! NSHTTPURLResponse
        let headFields = response.allHeaderFields as! [String:String]

        let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headFields, forURL: response.URL!)
Greasepaint answered 12/1, 2017 at 12:56 Comment(0)
O
0

In NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url), what happen if the url where the cookies are set is not a navigation response url (url that causes a navigation)? I notice the callback url where the cookies are set is never called in decidePolicyFor navigationResponse.

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    let response = navigationResponse.response as! HTTPURLResponse
    let cookies  = HTTPCookie.cookies(withResponseHeaderFields: response.allHeaderFields as! [String : String], for: response.url!) 
}

The above delegate is never executed for the callback url since the callback itself does not caused a page navigation.

cookies(withResponseHeaderFields:for:)

Oujda answered 21/2, 2017 at 4:57 Comment(0)
L
0

Solved for iOS 11 or above, the only thing you need to do is write the assíncronos method to get all the cookies this way:

webView.configuration.processPool = [[WKProcessPool alloc] init];
[[WKWebsiteDataStore defaultDataStore].httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * cookies) {
    //DO YOUR STUFF WITH THE 'cookies' VARIABLE
}
Legitimist answered 25/1, 2023 at 20:36 Comment(0)
S
-1

This post has useful information on cookie handling with WKWebView. According to this you should be able to set and retrieve cookies using the standard NSURLCache and NSHTTPCookie. He also refers to using WKProccessPool as per Stephan's comment.

Saskatchewan answered 30/11, 2015 at 18:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.