How to catch httpBody in WKWebView decidePolicyForNavigationAction? - ios

I switched from UIWebView to WKWebView in an iOS application. The webview loads a remote document which contains a POST-formular. Once the form is submitted a PDF is generated via PHP.
To be able to download and show this PDF properly I need to catch the POST content when the form is submitted - and do a manual URLRequest to download the file
I can catch the request as follows but the httpBody is empty in the code below:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ((WKNavigationActionPolicy) -> Void)) {
var request = navigationAction.request
if (request.httpMethod == "POST") {
request.httpBody // <-- is EMPTY
// as the httpBody is empty, the request below does not get the required result anymore
Alamofire.request(request).responseData { response in
...
let pdfData : Data = response.result.value!
...
let docController = UIDocumentInteractionController(url: urlToLocalPdfFile)
docController.delegate = self
docController.presentPreview(animated: true)
}
}
}
This did work with the UIWebView before. Is there a way to access the httpBody in the code above, or any workaround to accomplish the PDF-download (as described above)?

Related

WKWebView Localization iOS

I am loading a URL in WKWebview in which I am passing "Accept-Language" header for displaying page as per passed language.
var aRequest = URLRequest.init(url: MyUrl)
aRequest.setValue("fr-CA", forHTTPHeaderField: "Accept-Language")
self.webView.load(aRequest)
The above works properly in case of one of the URL and doesn't work in case of other. How to correctly use the header of Accept-Language?
I got inspired by How to set custom HTTP headers to requests made by a WKWebView and played around a bit with it.
This is what I ended up with:
Set the navigationDelegate of the webView to self and implement
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
guard navigationAction.request.value(forHTTPHeaderField: "Accept-Language") != "fr-CA" && navigationAction.navigationType != .other else {
decisionHandler(.allow)
return
}
decisionHandler(.cancel)
var request = navigationAction.request
request.setValue("fr-CA", forHTTPHeaderField: "Accept-Language")
webView.load(request)
}
The locales which worked for me are as below:
"fr-CA" - French
"es" - Spanish
"zh-CN" - Chinese

How can I obtain POST request body from WKWebView in Swift?

I have set up an API to respond to a POST request. Due to the nature of my app, I need to fulfill this POST request through a WKWebView, as opposed to using URLSession or Alamofire. I plan to use the data POST response body elsewhere in my app.
I was able to successfully construct a post request and load it in the following way:
request = URLRequest(url: previouslyDefinedApiURL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = previouslyDefinedBodyData
myWebView.load(request)
The code above works perfectly. My web view even displays the correct response from my API.
I implemented the WKNavigationDelegate method to which my code hooks into upon loading the request.
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if let response = navigationResponse.response as? HTTPURLResponse {
// Somehow get the response body?
}
decisionHandler(.allow)
}
The navigation response is of type URLResponse, which offers no way for extracting the body content of the response, which is a simple JSON. Somthing like the following:
{
status: "SUCCESS",
user_id: 1234,
transition_to: 'tabs'
}
Is there a swifty way of obtaining the response body from the wkwebview up in the native side of the code?
I'm answering my own question because I figured out how to do this on my own. Perhaps my question wasn't clear. My end goal was to extract the body content as a string.
Firstly, I ditched the callback I used:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
// do stuff
}
And instead, I used this callback and executed some javascript on the result:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.getElementById(\"my-id\").innerHTML", completionHandler: { (jsonRaw: Any?, error: Error?) in
guard let jsonString = jsonRaw as? String else { return }
let json = JSON(parseJSON: jsonString)
// do stuff
})
}
The id "my-id" comes from the response I constructed on the back end of this service, just in case the web view wrapped any other HTML around my response. My original intention was to do this without having to run any javascript, but I guess there's no other way. This works pretty well anyway.

httpCookieStore is only available on iOS 11.0 or newer [duplicate]

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?
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.
Finally, httpCookieStore for WKWebsiteDataStore landed in iOS 11.
https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor
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
Do not forget to add the solution code here
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)) }
}
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.
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 {
//...
}
}
}
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];
}];
}
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
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)
}
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.
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)
}
}
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!)
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:)
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
}
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.

how to get cookie from urlrequest in iOS wkwebview

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
var req = navigationAction.request;
let cookie = req.value(forHTTPHeaderField: "Cookie");
print(cookie) // always nil
decisionHandler(.allow);
}
I want to get the session after user login, but the cookie is always nil;how can I get it ?
Cookie is generally stored in variable document.cookie in browser.
You can access it by executing Javascript code. In this case simply "document.cookie" would return the cookies.
let cookieScript = "document.cookie;"
webView.evaluateJavaScript(cookieScript) { (response, error) in
if let response = response {
print(response as! String)
}
}
Why don't use WKWebView's API that appeared since iOS 11
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies() { cookies in
// do what you need with cookies
}
One advantage is that this method tracks HttpOnly cookies as well.

Getting all cookies from WKWebView

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?
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.
Finally, httpCookieStore for WKWebsiteDataStore landed in iOS 11.
https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor
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
Do not forget to add the solution code here
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)) }
}
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.
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 {
//...
}
}
}
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];
}];
}
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
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)
}
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.
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)
}
}
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!)
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:)
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
}
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.

Resources