Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WKWebView Persistent Storage of Cookies

I am using a WKWebView in my native iPhone application, on a website that allows login/registration, and stores the session information in cookies. I am trying to figure out how to persistently store the cookie information, so when the app restarts, the user still has their web session available.

I have 2 WKWebViews in the app, and they share a WKProcessPool. I start with a shared process pool:

WKProcessPool *processPool = [[WKProcessPool alloc] init];

Then for each WKWebView:

WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];  theConfiguration.processPool = processPool;  self.webView = [[WKWebView alloc] initWithFrame:frame configuration:theConfiguration]; 

When I log in using the first WKWebView, and then some time later pass the action to the 2nd WKWebView, the session is retained, so the cookies were successfully shared. However, when I relaunch the app, a new process pool is created and the session information is destroyed. Is there any way to get the session information to persist through an app restart?

like image 468
haplo1384 Avatar asked Sep 29 '16 13:09

haplo1384


People also ask

Is WKWebView deprecated?

Apple is phasing out UIWebView, which is used by developers for integrating web content into an app in a quick and secure manner. Apple is replacing UIWebView (and WebView) with WKWebView, an updated version, as UIWebView has been deprecated.

Does WKWebView share cookies with Safari?

You can share cookies between multiple WKWebView 's inside your app by utilising WKProcessPool . There is a way of passing cookie data from Safari to your app by combining SFSafariViewController (iOS 8 and below you will need to switch to Safari) with a custom URL scheme.

What is use of UIWebView or WKWebView?

A WKWebView object is a platform-native view that you use to incorporate web content seamlessly into your app's UI. A web view supports a full web-browsing experience, and presents HTML, CSS, and JavaScript content alongside your app's native views.

Is WKWebView secure?

The WKWebView is a modern API applying all the modern web security mechanisms, it's still maintained by Apple and gets updates. The good thing about WKWebView is that it does out-of-process rendering, so if the attackers find a memory corruption vulnerability in it, your application's process is still isolated.


2 Answers

This is actually a tough one because there's a) some bug that's still not solved by Apple (I think) and b) depends on what cookies you want, I think.

I wasn't able to test this now, but I can give you some pointers:

  1. Getting cookies from NSHTTPCookieStorage.sharedHTTPCookieStorage(). This one seems buggy, apparently the cookies aren't immediately saved for NSHTTPCookieStorage to find them. People suggest to trigger a save by resetting the process pool, but I don't know whether that reliably works. You might want to try that out for yourself, though.
  2. The process pool is not really what saves the cookies (though it defines whether they are shared as you correctly stated). The documentation says that's WKWebsiteDataStore, so I'd look that up. At least getting the cookies from there using fetchDataRecordsOfTypes:completionHandler: might be possible (not sure how to set them, though, and I assume you can't just save the store in user defaults for the same reason as for the process pool).
  3. Should you manage to get the cookies you need (or rather their values), but not be able to restore them as I guess will be the case, look here (basically it shows how to simply prepare the httprequest with them already, relevant part: [request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"]).
  4. If all else fails, check this. I know providing just link only answers is not good, but I can't copy all that and just want to add it for completeness sake.

One last thing in general: I said that your success might also depend on the type of cookie. That's because this answer states that cookies set by the server are not accessible via NSHTTPCookieStorage. I don't know whether that's relevant to you (but I guess it is, since you're probably looking for a session, i.e. server-set cookie, correct?) and I don't know whether this means that the other methods fail as well.

If all else fails, you might consider saving the users credentials somewhere (keychain, for example) and reuse them on the next app start to auth automatically. This might not restore all session data, but considering the user quit the app that's maybe actually desirable? Also perhaps certain values can be caught and saved for later use using an injected script, like mentioned here (obviously not for setting them at start, but maybe retrieve them at some point. You need to know how the site works then, of course).

I hope that could at least point you towards some new directions solving the issue. It's not as trivial as it should be, it seems (then again, session cookies are kind of a security relevant thing, so maybe hiding them away from the App is a conscious design choice by Apple...).

like image 161
Gero Avatar answered Oct 11 '22 08:10

Gero


I'm a little late in answering this but I would like to add some insights to the existing answers. The answer mentioned here provides valuable information into Cookie Persistence on WKWebView. However, there are a few caveats to it.

  1. WKWebView doesn't work well with NSHTTPCookieStorage, so for iOS 8, 9, 10 you will have to use UIWebView.
  2. You don't need to necessarily hold on to the WKWebView as a singleton but you do need to use the same instance of WKProcessPool every time to get the desired cookies again.
  3. It is preferable to set the cookies first using the setCookie method and then instantiate the WKWebView.

I would also like to highlight the iOS 11+ Solution in Swift.

let urlString = "http://127.0.0.1:8080" var webView: WKWebView! let group = DispatchGroup()      override func viewDidLoad() {     super.viewDidLoad()     self.setupWebView { [weak self] in         self?.loadURL()     } }  override func viewDidDisappear(_ animated: Bool) {     super.viewDidDisappear(animated)     if #available(iOS 11.0, *) {         self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in             self.setData(cookies, key: "cookies")         }     } else {         // Fallback on earlier versions     } }  private func loadURL() {     let urlRequest = URLRequest(url: URL(string: urlString)!)     self.webView.load(urlRequest) }      private func setupWebView(_ completion: @escaping () -> Void) {          func setup(config: WKWebViewConfiguration) {         self.webView = WKWebView(frame: CGRect.zero, configuration: config)         self.webView.navigationDelegate = self         self.webView.uiDelegate = self         self.webView.translatesAutoresizingMaskIntoConstraints = false         self.view.addSubview(self.webView)                  NSLayoutConstraint.activate([             self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),             self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),             self.webView.topAnchor.constraint(equalTo: self.view.topAnchor),             self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)])     }          self.configurationForWebView { config in         setup(config: config)         completion()     }      }  private func configurationForWebView(_ completion: @escaping (WKWebViewConfiguration) -> Void) {                  let configuration = WKWebViewConfiguration()          //Need to reuse the same process pool to achieve cookie persistence     let processPool: WKProcessPool      if let pool: WKProcessPool = self.getData(key: "pool")  {         processPool = pool     }     else {         processPool = WKProcessPool()         self.setData(processPool, key: "pool")     }      configuration.processPool = processPool          if let cookies: [HTTPCookie] = self.getData(key: "cookies") {                  for cookie in cookies {                          if #available(iOS 11.0, *) {                 group.enter()                 configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {                     print("Set cookie = \(cookie) with name = \(cookie.name)")                     self.group.leave()                 }             } else {                 // Fallback on earlier versions             }         }              }          group.notify(queue: DispatchQueue.main) {         completion(configuration)     } } 

Helper methods:

func setData(_ value: Any, key: String) {     let ud = UserDefaults.standard     let archivedPool = NSKeyedArchiver.archivedData(withRootObject: value)     ud.set(archivedPool, forKey: key) }  func getData<T>(key: String) -> T? {     let ud = UserDefaults.standard     if let val = ud.value(forKey: key) as? Data,         let obj = NSKeyedUnarchiver.unarchiveObject(with: val) as? T {         return obj     }          return nil } 

Edit: I had mentioned that it's preferable to instantiate WKWebView post setCookie calls. I ran into some issues wherein the setCookie completion handlers were not getting called the second time I tried to open the WKWebView. This seems to a be a bug in the WebKit. Therefore, I had to instantiate WKWebView first and then call setCookie on the configuration. Make sure to load the URL only after all the setCookie calls have returned.

like image 44
jarora Avatar answered Oct 11 '22 09:10

jarora