In my project, I’m using the WKWebView to display the Google OAuth popup. After it appears, the JS window.opener is null, and because of that, the original window cannot receive an auth token in a callback. This works perfectly fine in iOS 17.0 and earlier, but broken starting from 17.5. I’ve tested on the 18.0 too – same results the opener is always null no matter what I try.
HTML Part:
<html>
<head>
<script src="https://accounts.google.com/gsi/client" async></script>
</head>
<script type="text/javascript">
var global = {};
window.addEventListener("message", function (event) {
console.log("Trying to catch Google OAuth");
console.log(event);
});
function client() {
if (global.client === undefined) {
global.client = google.accounts.oauth2.initTokenClient({
client_id: '<client_id>',
scope: 'https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile',
callback: function (responseToken) {
console.log("=== Callback ===");
console.log(responseToken);
let auth = document.getElementById("auth");
auth.textContent = 'Token: ' + responseToken.access_token
}
});
}
return global.client;
}
function googleOauthClient() {
console.log("Trying to login");
let auth = document.getElementById("auth");
auth.textContent = 'In Progress...';
client().requestAccessToken();
}
</script>
<body>
<div id="center">
<a onclick="googleOauthClient()"><h1>Google Login Attempt</h1></a>
<h2 id="auth"></h2>
</div>
</body>
</html>
In iOS showing popup is implemented as:
#import "WebViewController.h"
#import "SafariServices/SafariServices.h"
@interface WebViewController () <WKNavigationDelegate, WKUIDelegate> {
WKWebView *web;
WKWebView *popupWebView;
}
@end
@implementation WebViewController
- (void)loadView {
[super loadView];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[[config preferences] setJavaScriptEnabled:YES];
[[config preferences] setJavaScriptCanOpenWindowsAutomatically:NO];
[[config defaultWebpagePreferences] setAllowsContentJavaScript:YES];
[config setAllowsInlineMediaPlayback:YES];
web = [[WKWebView alloc] initWithFrame:self.view.frame configuration:config];
[web setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[web setAllowsLinkPreview:YES];
[web setNavigationDelegate:self];
[web setUIDelegate:self];
[web setInspectable:YES];
[web setCustomUserAgent:[[web valueForKey:@"userAgent"] stringByAppendingString:@" Safari"]];
[[self view] addSubview:web];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *url = @"http://localhost:8080/oauth"; // A simple Spring Boot App where HTML is hosted
[web loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
}
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
if (navigationAction.targetFrame == nil) {
popupWebView = [[WKWebView alloc] initWithFrame:webView.frame configuration:configuration];
[popupWebView setCustomUserAgent: [webView customUserAgent]];
[popupWebView setUIDelegate:self];
[popupWebView setNavigationDelegate:self];
[popupWebView setInspectable:true];
[[[popupWebView configuration] defaultWebpagePreferences] setAllowsContentJavaScript:YES];
[[[popupWebView configuration] preferences] setJavaScriptCanOpenWindowsAutomatically:NO];
[[[popupWebView configuration] preferences] setJavaScriptEnabled:YES];
[[popupWebView configuration] setSuppressesIncrementalRendering:YES];
[webView addSubview:popupWebView];
[popupWebView loadRequest:[navigationAction.request copy]];
return popupWebView;
}
return nil;
}
- (void)webViewDidClose:(WKWebView *)webView {
[webView removeFromSuperview];
popupWebView = nil;
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"didFailProvisionalNavigation: %@", [error description]);
}
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"didFailNavigation: %@", [error description]);
}
- (void)webView:(WKWebView *)webView requestMediaCapturePermissionForOrigin:(WKSecurityOrigin *)origin initiatedByFrame:(WKFrameInfo *)frame
type:(WKMediaCaptureType)type decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler {
decisionHandler(WKPermissionDecisionGrant);
}
- (void)webView:(WKWebView *)webView requestDeviceOrientationAndMotionPermissionForOrigin:(WKSecurityOrigin *)origin
initiatedByFrame:(WKFrameInfo *)frame decisionHandler:(void (^)(WKPermissionDecision decision))decisionHandler {
decisionHandler(WKPermissionDecisionGrant);
}
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler {
completionHandler(nil);
}
@end
This works in the SFSafariViewController, but I cannot use it because I need to hide the Navigation and Status bar panels.
What else I’ve tried so far:
- Disabled in the Settings -> Safari -> Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Disable Removal of 3rd-Party Cookie Blocking (ITP);
- Disabled Popup Blocking;
- Added NSAllowsArbitraryLoads to Info.plist (added all possible security policies too);
- Added NSCrossWebsiteTrackingUsageDescription to Info.plist, and after enabled Allow Cross-Website Tracking in the App Settings;
And ideas about what I might be doing wrong? Or maybe some workaround I can utilize to get the Google OAuth token from the popup. Thanks in advance.
Vlady is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.