Inject cookies in WKWebView

| /

We often encounter scenarios where we need to inject cookies into WKWebView from the outside.

There are several specific schemes, generally speaking, the choice needs to be made in combination with the actual scene.

Here is a description of an actual scenario in a project I’ve encountered.

The background is that for some reason, we need to transform a piece of JS logic into Native logic.

The JS logic is to first request an API, and the response header of the API will have a Set-Cookie field and return a jump link.

After that, JS will jump to this link, because the two links are in the same domain, so no additional processing is required.

The logic of Native is outside the webview, requesting the API, and manually processing the Set-Cookie and jump logic in the response header. The question is how to handle Set-Cookie more securely.

Here is the direct conclusion.

The solution we adopt is to inject Cookies in two ways at the same time.

  1. Add Set-Cookie content in the request of loadRequest: to solve the Cookies required by SSR (Server-Side Rendering).
  2. Add the userScript that injects Cookies in WKWebView’s configuration.userContentController. In order to solve the problem of using cookies in the current page and in the secondary page.

The following is some pseudo code for reference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
- (void)dealWithWebView:(WKWebView *)webView
response:(NSHTTPURLResponse *)response {

// ...

// cookeis of resposne
NSArray<NSHTTPCookie *> *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:response.allHeaderFields forURL:response.URL];
cookies = [self reduceCookiesForDomain:url.host cookies:cookies];

// get url to redirect
NSURL *url = [self redirectUrlFromResponse:response];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (cookies.count) {
NSString *cookiesStr = [[NSHTTPCookie requestHeaderFieldsWithCookies:cookies] objectForKey:@"Cookie"];

// 1. do with request
if (cookiesStr.length) {
[request addValue:cookiesStr forHTTPHeaderField:@"Cookie"];
}

// 2. do with userScript
// injectTime is WKUserScriptInjectionTimeAtDocumentStart
NSString *cookiesSetStr = [self cookiesSetStringForCookies:cookies];
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:cookiesSetStr injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[webView.configuration.userContentController addUserScript: userScript];
}

[self.webView loadRequest::request];
// ...
}

// MARK: - some tools
- (NSArray<NSHTTPCookie *> *)reduceCookiesForDomain:(NSString *)domain cookies:(NSArray<NSHTTPCookie *> *)cookies {
NSMutableArray *newCookies = [@[] mutableCopy];
NSMutableSet<NSString *> *names = [NSMutableSet set];

NSString *domainWithDotPrefix = [domain hasPrefix:@"."]
? domain
: [NSString stringWithFormat:@".%@",domain?:@""];

for (NSHTTPCookie *cookie in cookies) {
NSString *name = cookie.name;
if ([name rangeOfString:@"'"].location != NSNotFound) { continue; }
if ([names containsObject:name]) { continue; }
if ([domain hasSuffix:cookie.domain] || [domainWithDotPrefix hasSuffix:cookie.domain]) {
[newCookies addObject:cookie];
}
}
return newCookies;
}

- (NSString *)cookiesSetStringForCookies:(NSArray<NSHTTPCookie *> *)cookies {
NSMutableArray *arr = [[NSMutableArray alloc] init];

for (NSHTTPCookie *cookie in cookies) {
NSString *cookieStr = [self stringForCookie:cookie];
if (cookieStr.length) {
NSString *cookieSetString = [NSString stringWithFormat:@"document.cookie = '%@';", cookieStr];
[arr addObject:cookieSetString];
}
}
return [arr componentsJoinedByString:@"\n"];
}

- (NSString *)stringForCookie:(NSHTTPCookie *)cookie {
if (!cookie) { return nil; }
NSString *expire = nil;

if (cookie.expiresDate) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = @"EEE, d MMM yyyy HH:mm:ss zzz";
expire = [NSString stringWithFormat:@"expires=%@;",[formatter stringFromDate:cookie.expiresDate]];
}
NSString *secure = cookie.secure ? @"secure;" : @"";

return [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@;%@%@",
cookie.name, cookie.value, cookie.domain, cookie.path,
expire?:@"",
secure];
}