9

I am desperately trying to add a custom cookie to a WKWebView instance (without using Javascript or similar workarounds).

From iOS 11 and upwards, Apple provides an API to do this: The WKWebViews WKWebsiteDataStore has a property httpCookieStore.

Here is my (example) code:

import UIKit
import WebKit

class ViewController: UIViewController {

    var webView: WKWebView!

    override func viewDidLoad() {
        webView = WKWebView()
        view.addSubview(webView)
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        let cookie = HTTPCookie(properties: [
            HTTPCookiePropertyKey.domain : "google.com",
            HTTPCookiePropertyKey.path : "/",
            HTTPCookiePropertyKey.secure : true,
            HTTPCookiePropertyKey.name : "someCookieKey",
            HTTPCookiePropertyKey.value : "someCookieValue"])!

        let cookieStore = webView.configuration.websiteDataStore.httpCookieStore
        cookieStore.setCookie(cookie) {
            DispatchQueue.main.async {
                self.webView.load(URLRequest(url: URL(string: "https://google.com")!))
            }
        }
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        webView.frame = view.bounds
    }
}

After this, if I use webView.configuration.websiteDataStore.httpCookieStore.getAllCookies(completionHandler:) I see that my cookie is in the list of cookies.

However, when inspecting the webview using Safari's developer tools (using a iOS Simulator of course) the cookie does not show up.

I also tried to inspect the traffic using a HTTP proxy (Charles in my case) to see if the cookie in included in my HTTP requests. It is not.

What am I doing wrong here? How can I add a cookie to WKWebView (on iOS versions 11 and up)?

naglerrr
  • 2,489
  • 1
  • 10
  • 22
  • don't think you can do that. Safaris has its own data store. So cookies, local storage... is not shared between your webview instance and Safari. That's why you can't see them in Sarafi – Ryan Ho Jun 21 '18 at 18:41
  • Please read my quesion again. I am attaching Safaris debugger to my WKWebView. – naglerrr Jun 21 '18 at 19:24
  • ah my bad. Anyway, you set the cookies, but you didn't send the cookies along with your URLRequest object. You should do something like urlRequest.setValue("someCookieKey = someCookieValue", forHTTPHeaderField: "Cookie") – Ryan Ho Jun 21 '18 at 19:28
  • 1
    Why would I need to do that? If the cookie is set, it should be included in any requests made by the WKWebView. If it isn‘t, thats a blocker honestly. While I can set the header for the inital request like you showed, i can not set is for any subsequent (ajax) requests, defeating the cookies purpose. – naglerrr Jun 21 '18 at 20:35
  • ah good question, if you want it to auto attach the cookies, just do `urlRequest. HTTPShouldHandleCookies = true` – Ryan Ho Jun 21 '18 at 20:42
  • I appreciate your feedback but unfortunately, this does nothing for me.The cookie still is not visible in Safari's developer tools and it is also not passed along with any requests i make. – naglerrr Jun 21 '18 at 20:51
  • What you see in the Safara debug tool is the cookies that Google web app set when returning the response (HTML Payload). What we were doing is submit the cookies to the web app, not writing it to the browser. Because google read the cookies you submit, but it didn't set it back before sending back the HTML page. So you can't see it in the Safari developer tool. Does that make sense? – Ryan Ho Jun 22 '18 at 14:49
  • @TaiHo With the safari debugger tool, he can check all the outgoing / incoming request. If there is cookies attached to the outgoing request, you can see them in the request headers. – Juky Nov 23 '18 at 10:34

3 Answers3

13

A bit late but I want to share a solution that worked for me, I hope it helps someone facing the same issue on iOS 12 as well.

Here is the simplified workflow I used:

  1. Instantiate a WKWebsiteDataStore object
  2. Set the custom cookie to its httpCookieStore
  3. Wait for the cookie to be set
  4. Instantiate the WKWebView
  5. Load the request

To do that I have created an extension of WKWebViewConfiguration:

extension WKWebViewConfiguration {

static func includeCookie(cookie:HTTPCookie, preferences:WKPreferences, completion: @escaping (WKWebViewConfiguration?) -> Void) {
     let config = WKWebViewConfiguration()
     config.preferences = preferences

     let dataStore = WKWebsiteDataStore.nonPersistent()

     DispatchQueue.main.async {
        let waitGroup = DispatchGroup()

        waitGroup.enter()
        dataStore.httpCookieStore.setCookie(cookie) {
            waitGroup.leave()
        }

        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

And for my example I have used it as follows:

override func viewDidLoad() {
  self.AddWebView()
}

private func addWebView(){

    let preferences = WKPreferences()
    preferences.javaScriptEnabled = true
    preferences.javaScriptCanOpenWindowsAutomatically = true

    let cookie = HTTPCookie(properties: [
        .domain: COOKIE_DOMAIN,
        .path: "/",
        .name: COOKIE_NAME,
        .value: myCookieValue,
        .secure: "TRUE",
        .expires: NSDate(timeIntervalSinceNow: 3600)
        ])

     //Makes sure the cookie is set before instantiating the webview and initiating the request
     if let myCookie = cookie {
        WKWebViewConfiguration.includeCookie(cookie: myCookie, preferences: preferences, completion: {
           [weak self] config in
              if let `self` = self {
                 if let configuration = config {
                    self.webView = WKWebView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width , height: self.view.frame.height), configuration: configuration)

                    self.view.addSubview(self.webView)
                    self.webView.load(self.customRequest)
                 }
              }
     }
}
SwissMark
  • 579
  • 4
  • 15
2

Any request to google.com redirects to www.google.com.

You would need to add www. to the domain field of the cookie. If the domain or the path doesn't match the request, the cookie won't be sent.

You can add the cookies explicitly.

let url = URL(string: "https://www.google.com")!
var request = URLRequest(url: url)
if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
    request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookies)
}
self.webView.load(request)
Onato
  • 8,758
  • 4
  • 42
  • 52
  • This does not work for me. The cookie is still not added to the request and seemingly not available in the `WKWebView`s storage (checked with Safari Dev Tools). – naglerrr Jun 22 '18 at 06:05
  • I updated the answer with the explicit method to set the cookies. There may well be a better solution, but this should get you going. – Onato Jun 22 '18 at 10:22
  • Thank you for your answer! This only sets the cookie for this one request unfortunately. The WebApp I am trying to embed (google.com is just an example here) uses subsequent AJAX requests, that also need the cookies applied. Because of this, I can unfortunately not use your suggestion. – naglerrr Jun 22 '18 at 13:34
  • @Onato it's not required to add www. in start because when you do inspecting on safari it not showing it comes through www or not. – Shauket Sheikh Oct 30 '18 at 05:13
-1

For iOS 11+ you really don't need to worry about cookies part it is very simple. Create your cookie like this. Don't make it secure true

let newcookie = HTTPCookie(properties: [
        .domain: "domain",
        .path: "/",
        .name: "name",
        .value: "vale",
        .secure: "FALSE",
        .expires: NSDate(timeIntervalSinceNow: 31556926)
        ])!

self.webview.configuration.websiteDataStore.httpCookieStore.setCookie(newcookie, completionHandler: {
                        // completion load your url request here. Better to add cookie in Request as well. like this way
       request.addCookies()
//enable cookie through request
        request.httpShouldHandleCookies = true

//load request in your webview.


                    })

Request extention add after cookie value ";"

extension URLRequest {

internal mutating func addCookies() {
    var cookiesStr: String = ""

        let mutableRequest = ((self as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
            cookiesStr += cookie.name + "=" + cookie.value + ";"                

            mutableRequest.setValue(cookiesStr, forHTTPHeaderField: "Cookie")
            self = mutableRequest as URLRequest

     }

}

Also i can see your are not setting WKWebViewConfiguration of wkwebview. Set configuration of your wkwebview. Also implement WKHTTPCookieStoreObserver and implement function.

    func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
    // Must implement otherwise wkwebview cookie not sync properly
    self.httpCookieStore.getAllCookies { (cookies) in
        cookies.forEach({ (cookie) in

        })
    }
}

Hope so this will work.

Shauket Sheikh
  • 2,159
  • 14
  • 31