WKWebViewでリダイレクト中のCookieを扱う

こんにちは、アイスタイルでiOS @cosmeの開発をしている小笠原です。
普段はプレイングマネージャー的な立ち位置で開発とマネージャーを兼務しています。
趣味ではFF14のiOS版タイマーアプリをほそぼそと開発しています。

UIWebVIewからWKWebViewに乗り換え

@cosmeアプリはまだネイティブ化されていない部分が多々有り、その部分をWebViewで代用しているのですが、UIWebVIewからWKWebViewに乗り換えた際にCookieが原因で正常に通信できない現象が発生したため、どのように解決したかを共有できればと思い書いてみました。

問題

UIWebViewのときには発生していなかったのですが、WKWebViewに乗り換えた際にリダイレクトにSet-Cookieがつく場合、次のRequestにCookieが乗らず、正常に通信できない問題が出ました。

リダイレクトにSet-Cookie有(問題のケース)

リダイレクトにSet-Cookieがある場合、次のリクエストにCookieは乗りません。

  1. 302(リダイレクト(Set-Cookie=Hoge1)
    302にSet-Cookieされたものは次のリクエストには乗らない
  2. Next Request(Cookie=””)

リダイレクトにSet-Cookie無(大丈夫なケース)

リダイレクトにSet-Cookieがない場合には次のリクエストにCookieが乗ります。

  1. 302(リダイレクト)
  2. 200(Set-Cookie=Hoge1)
  3. Next Request(Cookie=Hoge1)

対処

諸々調査検証した結果、WKWebViewのリダイレクト中のSet-Cookieについて、以下のことがわかってきました。

  1. WKWebViewでリダイレクト中のSet-Cookieを正しく処理することはできない
  2. 逆にWebviewで開いているページからの遷移では問題が起きない。

そこで2の部分に注目し、Webにhtmlを配置し、そこを経由することでリダイレクトにSet-Cookieがついても正常に通信できることがわかりました。

redirect.html

こんなのをWebに配置します。

<script>
    var el = document.createElement('a');
    var url = decodeURIComponent(window.location.search.substring(1));
    el.href = url;
    el.click();
</script>

使い方

アプリからは以下のようにredirect.htmlのパラメータに遷移先のURLを指定して使います。

let destUrlStr = "https://hogehoge.com"

var allowedCharacterSet = CharacterSet.alphanumerics
allowedCharacterSet.insert(charactersIn: "-._~")
guard let encodedURLString = destUrlStr.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else {
    return nil
}

let request = URLRequest(url: URL(string: "https://example.com/redirect.html?" + encodedURLString))
webView.load(request)

色々試して駄目だったケース

リダイレクトが発生したら次のリクエストにCookieを挿入できればいいのですが、WKWebViewはその手段が用意されていなかったので、なかなか手こずりました。

1. リダイレクトのイベントを捕捉して、次のリクエストにCookieを乗せる

このメソッドはリダイレクトが発生するたびに呼ばれますが、このタイミングではまだResponseを取得できませんでした。

func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    // リダイレクトが発生するたびに呼ばれるが、このタイミングではResponseは取得できない
    print("didReceiveServerRedirectForProvisionalNavigation = \(webView.url)")
}

2. Request直前のイベントを捕捉して、送信する前にCookieを挿入する

このメソッドはリダイレクト含むRequest前に呼ばれるため、以下のようにリダイレクト契機であれば、リクエストし直してCookieを挿入することはできそうですが、リダイレクト契機か判定する方法がないため駄目でした。

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    print("navigationAction Request = \(navigationAction.request)")
    if リダイレクト契機か? {
        self.loadURL()
        decisionHandler(.cancel)
    } else {
        decisionHandler(.allow)
    }
}

3. WKWebView.loadRequestをハックする

こちらの記事を参考にWKWebView.loadRequestをoverrideして内部でURLSessionを使い、自前で制御してしまう方法ですが、この場合にはWKWebViewDelegateの各種メソッドが呼ばれないため使用しませんでした。

サンプルコード

色々挙動を確認できるようにUIWebView/WKWebViewサンプルアプリを用意しました。
確認する際にはUIWebViewController, WKWebViewControllerのurlを設定ください。

使用したツール

今回、検証する際にCharlesを使用しました。
このツールは通信のモニタリングやRequest/Responseの編集ができたりと多岐にわたってかゆいところに手が届くため非常に使いやすかったです。

参考にした記事

WKWEBVIEW REDIRECT WITH COOKIES
WKWebView. My experience
WKWebViewのコード

おわりに

iOSでWebを見る場合、UIWebView/WKWebView/SFSafariViewControllerの選択肢があり、それぞれCookieを共有したり、しなかったり、iOSバージョンによって細かく挙動が違うこともあるので、色々大変だと思います。
また、今回の調査やサンプルコードは色々なパターンを検証した結果から導き出したものなので、間違っている所もあると思います。

最後まで読んでいただきありがとうございます。

iOS @cosmeの開発とマネージャー的な役を兼務しています。