7

I sent request to url1. url1 will redirect to url2 with cookie. url2 is for authorization. And I get code "302 found", which is correct. But when url2 redirect back to url1, the cookie lost. This results my request keeping redirect to url2, looping until failed.

Does any one met this kind of problem and know a solution? Thanks in advance.

This is a WKWebView problem. UIWebView will work fine. But somehow, I need to change to use WKWebView.

I already tried many solutions, such as Can I set the cookies to be used by a WKWebView?. These methods could handle the cookie in same domain. My problem is url1 and url2 have different domains. When redirect, the cookie is missing, which made the authorization failed, and resulted in looping between url1 and url2.

Does anybody met this problem and found out a workaround? Thanks in advance.

T. W
  • 233
  • 1
  • 3
  • 9

7 Answers7

7

Unfortunately, there is no official support for handling 302 redirect response in WKWebView.I came across the same issue too.There is a way to work around but may take some risks and not elegant.

  1. Override the WKWebView.load(_ request: URLRequest) and use URLSession to make the request instead.
  2. Make sure your URLSession can deal with 302 redirect by yourself which means you need to implement func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) to specify that you don't want the URLSession to handle 302 redirect automatically.
  3. Once you get the 302 response using URLSession the attached cookie will be set to HTTPCookieStorage.shared automatically.And then make a new request with those cookies and the url of response header field Location by WKWebView itself.

The code sample is Here.I subclass WKWebView and also deal with cookies in the most case including your 302 redirect case.If it works for you, please give me a star thx!

It is a really a hack way and be careful if you want use it in product.

Xingxing
  • 580
  • 1
  • 6
  • 17
  • Your WKWebView worked perfectly for my migration from UIWebView to WKWebView. Have you run into any issues with this code since you published it? Thanks you! – Justin Domnitz Nov 22 '17 at 20:30
  • Thank you so much! – Alex Shubin Nov 24 '17 at 06:57
  • @Justin Domnitz In iOS9 below the request will be sent twice instead of calling `loadHTMLString` in case of css/js not working(also commented at source code). I'm not sure if it is suitable for your case. – Xingxing Nov 27 '17 at 02:39
4

If you need subsequent requests from 'url2' to have their cookies set, you can try using 'WKUserScript' to set the values programmatically via javascript at document start like so:

WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] 
    initWithSource: @"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"
    injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
// again, use stringWithFormat: in the above line to inject your values programmatically
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];

However, if you require your cookies to be set on the initial load request, you can set them on NSMutableURLRequest like so:

WKWebView * webView = /*initialization stuff*/
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"YOUR URL"]];
[request addValue:@"TestCookieKey1=TestCookieValue1;TestCookieKey2=TestCookieValue2;" forHTTPHeaderField:@"Cookie"];
// use stringWithFormat: in the above line to inject your values programmatically
[webView loadRequest:request];

You can combine above two techniques to transfer cookie values from Native environment to WebView environment.

You can refer to this link for more advanced information on cookies.

tek3
  • 2,095
  • 2
  • 20
  • 50
  • Thanks. How to inject cookie in subsequent request url2? Write this user script in "navigationAction" or "navigationResponse" ? I see the similar answers in other stack overflow. I am confused which delegate method will update the cookie? Could you tell me? – T. W Jun 16 '17 at 14:12
  • @T.W You can have a look at this github repo for the code. https://github.com/haifengkao/YWebView .Its a custom WKWebview class with persistent cookies. – tek3 Jun 16 '17 at 20:46
  • 2
    @tek2 I am sorry. Both your answer and the github repo cannot solve this redirect problem. And I already tried them before I post this question. – T. W Jun 17 '17 at 13:51
  • 1
    @T.W I also faced the same issue. The github repo code seemed to work in my case. – tek3 Jun 19 '17 at 19:17
  • In your case, dose url1 and url2 have the same domain? Or different? In my case, they are different. – T. W Jun 19 '17 at 19:30
  • This technique will never work for redirect url. Ask your server developer to pass cookies in response whenever it change and after that inject cookie to document.cookie and request header. – Muhammad Shauket Oct 21 '18 at 05:56
2

Maybe late to the party, but I faced something pretty close to the above question last week and also from my side I concluded that for some reason cookies on 302 response are not handled correctly by WKWebView.

If your problem is similar to the one i'm facing with:

you need to pass some cookies from a redirect required for a subsequent authentication stage

Then you can easily interchange your WKWebView with the one I provided into this gist.

and use it like follow:

let myWebView = SAWKWebView(frame: .zero)
...
myWebView.load(aRequest, handleRedirect: true, cookies: [myCookies])

cookies is not required and can be nil, depending on your setup.

SAWKWebView class inheriting from WKWebView and workaround the above problem by using directly URLSession and its delegate.

This way I could rely into a sufficient and safe way to break into the response 302 (aka redirect) and handle cookies manually with HTTPStorageCookie without relying onto javascript and document since in my experience it doesn't always work reliable.

Once the required redirect is performed I render the content as HTML and continue as needed.

valvoline
  • 7,737
  • 3
  • 47
  • 52
0

Another solution worked for me:

Adopt WKNavigationDelegate, webView.navigationDelegate = self, then:

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    /// Cancel the navigation action
    decisionHandler(.cancel)

    /// Navigate yourself 
    let request = NSMutableURLRequest(url: url)
    request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: Auth.getAuthCookies())
    webView.load(request as URLRequest)
}

This was all that was needed for my simple case, but I imagine others may also need to copy the other request info that's stored in navigationAction.request like the navigationAction.request.allHTTPHeaderFields

Alexandre G
  • 1,655
  • 20
  • 30
0

I've tried the solutions suggested in the other answers but finally the one that seems to work better in my case was implement this WKNavigationDelegate's method:

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {

    if let url = webView.url {
        webView.load(authenticatedRequest(URLRequest(url: url)))
    }
}

Where authenticatedRequest() is a private function that given a request, it returns the same request with the headers/cookies/whatever your service needs to authenticate.

Hope someone find this solution helpful.

jorgifumi
  • 195
  • 1
  • 9
0

recently i faced same problem and i fixed this problem by doing this.I am sure cookie lost happen only if you are using iOS < 11, for this you need to apply some work around as i applied recently.

1- pass updated cookies in response from server side.Whenever it changed from server,

Read cookie from response like this way.

if  let allHttpHeaders = response.allHeaderFields as? [String: String] {
            let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHttpHeaders, for: responseUrl)
            for cookie in cookies {
                if cookie.name == "cookie name" {
                    // compare with old cookie value so it will show your cookie is changed now.
                    if "your_old_cookie_value" != cookie.value {

                            //update cookie in Script and request header
                            // reload your page here again.
                        break
                    }
                }
            }
        }

This is the minimal change by that you can fix cookie lost issue, for iOS >=11 should not be an issue, for ios 11 and above use httpcookiestore

Let me know if you have any question.

Muhammad Shauket
  • 2,643
  • 19
  • 40
-2

I suppose the proper solution for iOS 11 is to use the WKHTTPCookieStore. It's designed to solve exactly this issue as per this keynote.

Teodor Todorov
  • 66
  • 1
  • 10