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)
}
}