Nadav96 Nadav96 - 1 month ago 27
iOS Question

WKWebview injecting cookie header cause redirect loop

I'm trying to inject session cookies I've acquired separately into a WKWebview request, which turns out to be quite a pain...

I managed to inject the session cookies using this solution, as follows:

// Acquiring the cookies
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: s.request!.url!)

//Appending all the cookies into one raw string.
var cookiesRawString = ""
for c in cookies {
cookiesRawString += "\(c.name)=\(c.value); "
}

var req: URLRequest = try! URLRequest(url: URL, method: method)

// Then the injection itself
request.setValue(cookiesRawString, forHTTPHeaderField: "Cookie")

webView.load(req)


Let me quickly explain the server logic with pseudo code:


  1. Server receive call to endpoint /endpoint1 with initial session cookies appended

  2. It then proceed to redirect the client to /endpoint2 with user generated token appended in the url.

  3. requesting the second endpoint with token appended results in the final redirect to /endpoint3 with Set-Cookie header containing one time session cookies

  4. /endpoint3 with the one time session cookies appended results in 200 response, and the user is recognized.



The problem is that for some reason the when I append the cookies to the initial request using the method above, it results with a redirect loop, while on the Android platform it works flawless (I used there a similar injection method).

The only difference that I saw between them was that the android app injected the cookies only on the initial request, and all the subsequent redirect calls were without those session cookies.
While the ios repeated the initial session cookies on all the redirect calls (even ignoring the server set-cookie header and appending the initial session cookies..).

Am I doing something wrong? How can I make the wkwebview use the injected cookies only on the initial request?

Edit 1: Also tried falling back to UIWebview, but it produces the same results, it seems that injecting the cookies as a header is not good, but I tried using HTTPCookieStorage, but it won't save the cookies!

// the count is 7
var cookiesCount = HTTPCookieStorage.shared.cookies(for: s.request!.url!)?.count

let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: s.request!.url!)

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

// Count is still 7!
cookiesCount = HTTPCookieStorage.shared.cookies(for: s.request!.url!)?.count


Edit 2:
Well I found out the UIWebview is using a global instance of the cookie storage, the same as alamofire (which I've used to fetch the session cookies), so there was no need to add the cookies manually, the site recognized the user.

But I still prefer to use WKWebview, as the UIWebview memory leak sky rockets (over 100 mb after couple of web pages navigation!).

Is there a way to use the global cookie jar (used by alamofire) in WKWebview??

Answer

I managed to get it working on WKWebview, using a hackish solution:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction:
               WKNavigationAction, decisionHandler: 
               @escaping (WKNavigationActionPolicy) -> Void) {

    let url = navigationAction.request.url!.absoluteString

    if UserAppendix.isLogin && url != previousNavigateUrl {
        previousNavigateUrl = url
        if url.contains("/endpoint1") {
            let headerFields = navigationAction.request.allHTTPHeaderFields
            let headerIsPresent = headerFields!.keys.contains("Cookie")

            if headerIsPresent {
                decisionHandler(WKNavigationActionPolicy.allow)
            } else {

                var req = URLRequest(url: navigationAction.request.url!)
                let cookies = NetworkAppendix.httpSessionCookies
                let values = HTTPCookie.requestHeaderFields(with: cookies)
                req.allHTTPHeaderFields = values
                webView.load(req)

                decisionHandler(WKNavigationActionPolicy.cancel)
            }
        }
        else if firstTime {
            firstTime = false

            let req = URLRequest(url: navigationAction.request.url!)
            webView.load(req)

            decisionHandler(WKNavigationActionPolicy.cancel)


        }
        else {
            decisionHandler(.allow)
        }
    }
    else {
        decisionHandler(.allow)
    }
}

I set the cookies on the first request, and on the second request break the flow and create a new request (with the redirect url), to avoid the session cookies I set on the first request, all subsequent requests are treated the same.

I know it's not perfect, but it gets the job done.