For one of our native iOS projects, part of the functionality is implemented using a web view. In these web views, the users need to be authenticated, which happens by injecting authentication cookies. But in one specific case, this user authentication information got lost for no clear reason. We set out to find a solution – let’s show you how we did it!
Let’s start by detailing the problem. For the user to be authenticated at the server side, we inject the user’s authentication cookies into the first request. However, we discovered that when the server responds with a 302 redirect, WKWebview does not pass the cookies set by the server to the redirect URL.
In other words, they’re not being incorporated into the redirected request, and as a result, the user would not be authenticated in the webview. This bug would, of course, be a show stopper for the project – luckily we found a workaround.
The most obvious solution would be to handle that first request manually. When it returns with a 302 redirect, we should, yet again, inject those cookies into that request. However, we can use a special delegate method of the URLSession to intercept the redirect before it is triggered. Here we will inject our authentication cookies and return the altered URLRequest. We’ll then pass the response of that redirected request back to the WKWebView. From then on, the webview flow will take its intended flow.
It only seems safe and possible to add this mechanism to the initial load/request. Consecutive load requests would not necessarily be full page loads, so it would prove nearly impossible to inject those responses at the correct point.
You are welcome to add suggestions and/or improvements to our GitHub page.
Now let’s dive into a little more detail...
For this to work, we need to make a custom subclass of the WKWebView.
{% c-block language="js" %}
class WKCookieWebView : WKWebView {
/* // Custom implementation */
}
{% c-block-end %}
First, we’ll override the load(_:) function. Here we will do the first request manually via a URLSession.
{% c-block language="js" %}
private func requestWithCookieHandling(_ request: URLRequest, success: @escaping (URLRequest, HTTPURLResponse?, Data?) -> Void, failure: @escaping () -> Void) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: request) { (data, response, error) in
if let _ = error {
failure()
} else {
/* // Handle success */
}
task.resume()
}
}
{% c-block-end %}
The URLSessionTaskDelegate will call a specific function when a redirect will occur. We will use this function to add the cookies from the response to the new request.
{% c-block language="js" %}
extension WKCookieWebView : URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
syncCookies(request) { (newRequest) in
completionHandler(newRequest)
}
}
}
{% c-block-end %}
The necessary cookies will be stored in the shared HTTPCookieStorage. There are a number of ways to aggregate these cookies.
Our function syncCookies(_:) will return an enriched URLRequest with these cookies. When we have a URLSessionTask or a valid url from the URLRequest, we will request the cookies set for that task or request. Otherwise we will just request all the cookies present at that moment. The latter is not very performant, but intended as a last resort. In our case, we will always have a task or request, so we can add only the necessary cookies.
When we’ve received the cookies, we then convert them to a request header fields dictionary and add this as a request header. This way the authentication cookies will be present in the new request.
{% c-block language="js" %}
extension WKCookieWebView {
private func syncCookies(_ request: URLRequest, _ task: URLSessionTask? = nil, _ completion: @escaping
(URLRequest) -> Void) {
var request = request
var cookiesArray: [HTTPCookie] = []
if let task = task {
HTTPCookieStorage.shared.getCookiesFor(task, completionHandler: { (cookies) in
if let cookies = cookies {
cookiesArray.append(contentsOf: cookies)
let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
if let cookieStr = cookieDict["Cookie"] {
request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
}
}
completion(request)
})
} else if let url = request.url {
if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
cookiesArray.append(contentsOf: cookies)
}
let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
if let cookieStr = cookieDict["Cookie"] {
request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
}
completion(request)
} else {
if let cookies = HTTPCookieStorage.shared.cookies {
cookiesArray.append(contentsOf: cookies)
}
let cookieDict = HTTPCookie.requestHeaderFields(with: cookiesArray)
if let cookieStr = cookieDict["Cookie"] {
request.addValue(cookieStr, forHTTPHeaderField: "Cookie")
}
completion(request)
}
}
}
{% c-block-end %}
When the redirected request returns successfully or failed, we pass it through to the WKWebView. From then on, everything is again handled internally by the WKWebView and the system can do its thing!
The full custom subclass can be found on GitHub.
Did you just say, “ah, brilliant, I needed that!” out loud? We can do you one better: we’re growing our team. Hop over to our jobs page, and maybe next time you’ll be on the front row of these solutions…
Follow us on LinkedIn for insights, learnings, use cases and more.
Let’s get to know each other better and explore how we can help your business embark on a journey towards digitally enabled success.
Working in collaboration with Penny Black and its joint venture incubators, Agfa and ninepointfive, November Five delivered a technology project that strengthened Penny Black’s business case and lowered its backers’ investment risk.
Our Vanbreda Healthcare platform has been awarded a UX Design Awards nomination by International Design Center Berlin – the leading, independent design institution promoting design as a driver for business and social innovation.
November Five was named one of Fast Company’s global 100 Best Workplaces for Innovators in both 2020 and 2021. This annual list, developed in collaboration with Accenture, recognises and honors the top 100 businesses from different industries that inspire, support and promote innovation at all levels. For the consecutive year, November Five was the single Belgian workplace listed.
Fast Company is the world's leading progressive business media brand, with a unique editorial focus on innovation in technology, ethical economics, leadership, and design. Written for, by, and about the most progressive business leaders, Fast Company and FastCompany.com inspire readers and users to think beyond traditional boundaries, lead conversations and create the future of business.