WKWebview could not load asset files because of Authorization

625 views Asked by At

I have a WKWebview applying AWS Cognito. Every request to the server has to be added Authorization into request header.

let access_token = "Bearer \(key)"
let header: [String: String] = [
    "Authorization": access_token
]
if let url = URL(string: "https://myserverdomain.amazonaws.com/api/v3/graphs?date=2020-08-28") {
    var request: URLRequest = URLRequest(url: url)
    request.allHTTPHeaderFields = header
    wkWebview.load(request)
}

With this code, I already can load the page content but CSS in the page. I checked with chrome (using ModHeader chrome extension to add header) and it works, show correctly, also Android.

I inspected by Chrome and the CSS link in < head > tag like this, it is not the same folder with the HTML file (I don't know if it is the reason).

<link rel="stylesheet" type="text/css" href="https://myserverdomain.amazonaws.com/assets/graphs/style.css"></script>

I can load the css content only with the code:

let access_token = "Bearer \(key)"
let header: [String: String] = [
    "Authorization": access_token
]
if let url = URL(string: "https://myserverdomain.amazonaws.com/assets/graphs/style.css") {
    var request: URLRequest = URLRequest(url: url)
    request.allHTTPHeaderFields = header
    wkWebview.load(request)
}

UIWebview was deprecated, Is there any way to set WKWebview with a global header as always?

Thank you for your help.

1

There are 1 answers

1
iUrii On

You can redirect all webview's requests to your URLSession with your configuration. To do that you can register your custom URLProtocol for https scheme. There is a hack for WKWebView to intercept url requests with WKBrowsingContextController private class and your URLProtocol implementation e.g.:

class MiddlewareURLProtocol : URLProtocol {
    static let handledKey = "handled"
    
    lazy var session : URLSession = {
        // Config your headers
        let configuration = URLSessionConfiguration.default
        //configuration.httpAdditionalHeaders = ["Authorization" : "..."]

        return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    }()
    
    var sessionTask : URLSessionTask?
    override var task: URLSessionTask? {
        return sessionTask
    }
    
    static func registerClass() {
        let sel = NSSelectorFromString("registerSchemeForCustomProtocol:")
        
        if let cls = NSClassFromString("WKBrowsingContextController") as? NSObject.Type, cls.responds(to:sel) {
            // Register https protocol
            cls.perform(sel, with: "https")
        }
        URLProtocol.registerClass(Self.self)
    }
    
    override class func canInit(with request: URLRequest) -> Bool {
        return URLProtocol.property(forKey: Self.handledKey, in: request) == nil
    }
    
    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }
    
    override class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool {
        super.requestIsCacheEquivalent(a, to: b)
    }
    
    override func startLoading() {
        let redirect = (request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
        URLProtocol.setProperty(true, forKey: Self.handledKey, in: redirect)
        
        sessionTask = session.dataTask(with: redirect as URLRequest)
        
        task?.resume()
    }
    
    override func stopLoading() {
        task?.cancel()
    }
    
}

extension MiddlewareURLProtocol : URLSessionDataDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let err = error {
            client?.urlProtocol(self, didFailWithError: err)
        }
        else {
            client?.urlProtocolDidFinishLoading(self)
        }
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
        completionHandler(.allow)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        client?.urlProtocol(self, didLoad: data)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
        completionHandler(proposedResponse)
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
        let redirect = (request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
        Self.removeProperty(forKey: Self.handledKey, in: redirect)
        
        client?.urlProtocol(self, wasRedirectedTo: redirect as URLRequest, redirectResponse: response)
        
        self.task?.cancel()
        
        let error = NSError(domain: NSCocoaErrorDomain, code: CocoaError.Code.userCancelled.rawValue, userInfo: nil)
        client?.urlProtocol(self, didFailWithError: error)
    }
}

Just register your protocol on app start to handle all requests:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    MiddlewareURLProtocol.registerClass()
    ...
}

NOTE: To prevent Apple static checks for private classes you can store class names in the array:

let className = ["Controller", "Context", "Browsing", "WK"].reversed().joined()