36

There is a file (CSV) that I want to download. It is behind a login screen on a website. I wanted to show a WKWebView to allow the user to log in and then have the app download the file after they had logged in.

I've tried downloading the file outside of WKWebView after the user has logged in to the website, but the session data seems to be sandboxed because it downloads an html document with the login form instead of the desired file.

I've also tried adding a WKUserScript to the WKUserContentController object, but the script doesn't get run when a non-HTML file is loaded.

Is there a way for me to access this file while allowing users to log in via the WKWebView?

Devin McKaskle
  • 802
  • 1
  • 8
  • 15

5 Answers5

97

Right now, WKWebView instances will ignore any of the default networking storages (NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) and also the standard networking classes you can use to customize the network requests (NSURLProtocol, etc.).

So the cookies of the WKWebView instance are not stored in the standard Cookie storage of your App, and so NSURLSession/NSURLConnection which only uses the standard Cookie storage has no access to the cookies of WKWebView (and exactly this is probably the problem you have: the „login status“ is most likely stored in a cookie, but NSURLSession/NSURLConnection won’t see the cookie).

The same is the case for the cache, for the credentials etc. WKWebView has its own private storages and therefore does not play well with the standard Cocoa networking classes.

You also can’t customize the requests (add your own custom HTTP headers, modify existing headers, etc), use your own custom URL schemes etc, because also NSURLProtocol is not supported by WKWebView.

So right now WKWebView is pretty useless for many Apps, because it does not participate with the standard networking APIs of Cocoa.

I still hope that Apple will change this until iOS 8 gets released, because otherwise WKWebView will be useless for many Apps, and we are probably stick with UIWebView a little bit longer.

So send bug reports to Apple, so Apple gets to know that these issues are serious and needs to be fixed.

Alex
  • 1,471
  • 11
  • 4
  • 1
    Interesting info, how did you discover it? – user454322 Aug 27 '14 at 06:16
  • 12
    I hope more people discover this post before they implement. This is totally not obvious and I would consider it as a serious bug. Please up vote this reply to help get more visibility, and make sure to submit to bugreport.apple.com – Bao Lei Sep 19 '14 at 17:34
  • @BaoLei Just wasted 4 hours with this. :( – Rudolf Adamkovič Oct 02 '14 at 07:40
  • 1
    Spoiler: As of iOS8.2 beta, no changes to WebKit2 to accommodate all these problems. – Léo Natan Dec 05 '14 at 13:52
  • @Alex :: Are those issue still there? – Sourav Gupta Nov 17 '15 at 16:39
  • 4
    Some of the issue were resolved with iOS 9. You can now use the „WKWebsiteDataStore“ class to ask for cookies, caches etc. For some Apps this might be sufficient. But the main limitation remains: There’s still no support for NSURLProtocol, so there’s still no way in filtering and customizing the network traffic of WKWebView. – Alex Nov 18 '15 at 18:17
15

Have you checked the response cookies coming back from the request. You could use a delegate method like this.

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];

    for (NSHTTPCookie *cookie in cookies) {
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }

    decisionHandler(WKNavigationResponsePolicyAllow);
}
Devin McKaskle
  • 802
  • 1
  • 8
  • 15
Nick Anger
  • 172
  • 1
  • 9
  • 2
    No cookies were created for me using this. – Devin McKaskle Dec 13 '14 at 14:06
  • This worked for me. Once I inserted this code into my delegate, logged out, and back in again, (As I discovered the cookies only came down via the successful login notification response, and were basically unreachable after that) I was subsequently able to download files via `NSURLSession` using the same session after that point. – TiM Jul 08 '16 at 03:13
1

I've accomplished something similar to what you're trying to do:

  • use the web view to login as you're already doing
  • set a navigationDelegate on your webview
  • implement -webView:decidePolicyForNavigationResponse:decisionHandler in your delegate
  • when that delegate method is called (after the user logs in), you can inspect navigationResponse.response (cast it to NSHTTPURLResponse*) and look for a Set-Cookie header that contains the session info you'll need for authenticated sessions.
  • you can then download the CSV by manually specifying the Cookie header in your request with the cookies specified in the response.

Note that the delegate methods are only called for "main frame" requests. Which means that AJAX requests or inner frames will not trigger it. The whole page must refresh for this to work.

If you need to trigger behavior for AJAX requests, iframes etc you'll need to inject some javascript.

Sebastien Martin
  • 1,341
  • 11
  • 25
  • How is this different from [Nick's answer](http://stackoverflow.com/a/27450860/1223950)? I tried that at the time he answered and it didn't work for me. – Devin McKaskle Feb 25 '15 at 16:35
1

You can obtain the cookie via Javascript:

You could then use the obtained cookie to download the file manually:

webView.evaluateJavaScript("(function() { return document.cookie })()", completionHandler: { (response, error) -> Void in
  let cookie = response as! String
  let request = NSMutableURLRequest(URL: docURL)
  request.setValue(cookie, forHTTPHeaderField:  "Cookie")

  NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) in
    // Your CSV file will be in the response object
  }).resume()
})
Pieter Meiresone
  • 1,910
  • 1
  • 22
  • 22
0

I do not seem to have the session cookie when I try a request from userContentController didReceiveScriptMessage.

However, when called from decidePolicyForNavigationAction, the following code detects that I'm logged in.

     let urlPath: String = "<api endpoint at url at which you are logged in>"

    let url = NSURL(string: urlPath)
    let session = NSURLSession.sharedSession()
    let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in

        let string1 = NSString(data: data, encoding: NSUTF8StringEncoding)
        println(string1)
        println(data)
        if(error != nil) {
            println("Error sending token to server")
            // Print any error to the console
            println(error.localizedDescription)
        }
        var err: NSError?

    })

    task.resume()