Copied to clipboard
series
Series
Insights

WKWebView redirect with cookies

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!

17/7/2023
4
min read

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.

High-level solution

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...

Setup

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

Handling the redirect

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


Adding the cookies

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


Finishing of the redirect

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…

Jens Reynders

Engineer

Jens Reynders

Engineer

17/7/2023
4
min read

We help companies
succeed in the digital age

Stay up-to-date with November Five

Follow us on LinkedIn for insights, learnings, use cases and more.

Looking for a partner that thinks beyond delivery?

We help companies
succeed in the digital age

Let’s get to know each other better and explore how we can help your business embark on a journey towards digitally enabled success.

CONTACT US
Series
DISCOVER MORE INSIGHTS
Chevron
About Fast Company’s ‘Best Workplace for Innovators’

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.

Jeroen Van Winckel

Product Strategy Designer

Ralph Van Tongelen

Finance Director

Office

Office

Dario Prskalo

Associate to the executive team

Brecht Spileers

Chief of Staff & Director Corporate Strategy

Emily Stewart

Senior Content Writer

Rindert Dalstra

Brand & Marketing Director

Robin Van den Bergh

Managing Director at Appmiral

Maarten Raemdonck

Co-founder & Managing Director at Spencer

Phillip Vandervoort

Executive advisor - Strategy

Vincent Bruyneel

CFO & COO

David Du Pré

Executive advisor

Marc Wojciechowski

Assistant Director

Muriel Mwema

Director Product Management & Delivery

Nick Verbaendert

Co-Founder & Director Business Operations

David De Bels

Product Owner at Appmiral

Tom Vroemans

Co-founder & CEO

Veronique Verhees

Talent Manager

Jens Reynders

Engineer

Michiel Van Nueten

UI Designer

Samuel De Pooter

Engineer

Bert Hofmans

UI Designer

Stijn Symons

Director Architecture

Vincent Pauwels

Co-founder & Director Experience Design

Thomas Van Sundert

Co-founder & Director Engineering

Justin Mol

Director Client Partnerships

Leslie De Cuyper

Client Partner

Ruben Van den Bossche

Chief Operating Officer

Nikki Jacobs

Managing Director at The Market