WKWebView Authorization Challenge fails to refresh

I have a url that I want to stream, the stream is protected by basic auth.

I am using URLAuthenticationChallenge (wkwebview navigation delegate function) method to provide my credentials. the credential changes with every stream url.

The issue I am facing is that URLAuthenticationChallenge is never called upon refresh or on new URL request until the app is killed and opened again.

I have tried:

  • clearing cooking
  • setting cache to ignore
  • opening an another blank url
  • using child view approach
  • setting wkwebview instance to nil

I always end up with 401 in decidePolicy (wkwebview delegate functions) on subsequent refresh call. Upon inspecting I see that it is using my previous used credentials and never calling challenge to update.

*Backend has a no-cache policy in its header.

**I am using http resource, the aim is to have streaming service on iOS.

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {

    @IBOutlet weak var wkWebView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let url = URL(string: "http://some-ip-address/some-path")!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        wkWebView.configuration.allowsInlineMediaPlayback = true
        wkWebView.navigationDelegate = self
        wkWebView.load(request)
        
    }
    
    // this is just called once. we want to call it everytime our page refreshes.
    // we have tried clearing cache and cookies but failed to call challenge
    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge,
                 completionHandler: @escaping(URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        let credential = URLCredential(user: "user",
                                               password: "password",
                                               persistence: .forSession)
        completionHandler(.useCredential, credential)
    }
    
}

How we avoided or bypassed it using 2 load requests, as webview on subsequent reload or calls doesnt load the video and ends up with 401 status code and fails to call for refresh of challenge authentication

We use 2 load, load via url session and then load web view. url sessions updates the username and password respectivly against web-load calls as well, thus loading video previews.

This is not a fix but we want to portray that there's an issue. we would like to have your opinion, comment and suggestions.

class ViewController: UIViewController {
    @IBOutlet weak var viewContainer: UIView!
    var currentWebView: WKWebView!
    private var authenticationSession: URLSession?
    let streams = [
        "First Stream": ("url of the stream", "user", "password"),
        "Second Stream": ("url of the stream 2", "newuser", "secret")
    ]
    override func viewDidLoad() {
        super.viewDidLoad()
        setupWebView()
    }
    private func setupWebView() {
        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []
        let preferences = WKPreferences()
        preferences.javaScriptCanOpenWindowsAutomatically = false
        config.preferences = preferences
        config.websiteDataStore = WKWebsiteDataStore.default()
        currentWebView?.removeFromSuperview()
        currentWebView = WKWebView(frame: viewContainer.bounds, configuration: config)
        currentWebView.navigationDelegate = self
        currentWebView.scrollView.isScrollEnabled = false
        currentWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        currentWebView.isHidden = true
        viewContainer.addSubview(currentWebView)
    }
    private func setupURLSession(username: String, password: String) {
        let sessionConfig = URLSessionConfiguration.default
        sessionConfig.waitsForConnectivity = true
        sessionConfig.timeoutIntervalForRequest = 30
        sessionConfig.timeoutIntervalForResource = 300
        class AuthenticationDelegate: NSObject, URLSessionDelegate {
            let username: String
            let password: String
            init(username: String, password: String) {
                self.username = username
                self.password = password
                super.init()
            }
            func urlSession(_ session: URLSession,
                            didReceive challenge: URLAuthenticationChallenge,
                            completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                let credential = URLCredential(user: username, password: password, persistence: .forSession)
                completionHandler(.useCredential, credential)
            }
        }
        let delegate = AuthenticationDelegate(username: username, password: password)
        authenticationSession = URLSession(configuration: sessionConfig, delegate: delegate, delegateQueue: nil)
    }
    func loadStream(named streamName: String) async {
        guard let stream = streams[streamName] else {
            return
        }
        let (urlString, username, password) = stream
        setupWebView()
        currentWebView.isHidden = false
        await clearWebViewSession()
        self.setupURLSession(username: username, password: password)
        self.performStreamLoad(
            urlString: urlString,
            username: username,
            password: password
        )
    }
    private func performStreamLoad(urlString: String, username: String, password: String) {
        guard let url = URL(string: urlString) else {
            print("Invalid URL")
            return
        }
        var request = URLRequest(url: url)
        request.cachePolicy = .reloadIgnoringLocalCacheData
        let loginString = "\(username):\(password)"
        if let loginData = loginString.data(using: .utf8) {
            let base64LoginString = loginData.base64EncodedString()
            request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
        }
        request.setValue("no-cache", forHTTPHeaderField: "Cache-Control")
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        let protectionSpace = URLProtectionSpace(
            host: url.host ?? "",
            port: url.port ?? 80,
            protocol: url.scheme,
            realm: nil,
            authenticationMethod: NSURLAuthenticationMethodHTTPBasic
        )
        let credential = URLCredential(
            user: username,
            password: password,
            persistence: .forSession
        )
        URLCredentialStorage.shared.setDefaultCredential(credential, for: protectionSpace)
        authenticationSession?.dataTask(with: request) {
            [weak self] _,
            response,
            error in
            DispatchQueue.main.async {
                if let httpResponse = response as? HTTPURLResponse,
                   httpResponse.statusCode == 200 {
                    self?.currentWebView.load(request)
                } else {
                    print("Authentication failed with status code: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
                    self?.retryWithModifiedRequest(request: request, username: username, password: password)
                }
            }
        }.resume()
    }
    private func retryWithModifiedRequest(request: URLRequest, username: String, password: String) {
        var modifiedRequest = request
        modifiedRequest.setValue("Basic realm=\"Restricted Area\"", forHTTPHeaderField: "WWW-Authenticate")
        currentWebView.load(modifiedRequest)
    }
}
extension ViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge,
                 completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let url = webView.url else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        if let stream = streams.first(where: { $0.value.0 == url.absoluteString.split(separator: "?").first.map(String.init) ?? "" }) {
            let (_, username, password) = stream.value
            let credential = URLCredential(user: username, password: password, persistence: .permanent)
            completionHandler(.useCredential, credential)
        } else {
            completionHandler(.performDefaultHandling, nil)
        }
    }
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decisionHandler(.allow)
    }
}
WKWebView Authorization Challenge fails to refresh
 
 
Q