How to init WKWebView with already existing bearer token - ios

I'm implementing an iOS app which authenticates to my web service and receives a token. I have embedded a WKWebView to my app so that I can open one specific web page in which I have javascript that tries to read the token from local storage. How can I init the WKWebView so that my token is accessible from the local storage?
Edit: Here is a discussion telling a way to enable local storage: iOS WKWebView does not support local storage but it seems that adding an item to local storage from app code is not possible.

Try setting the token in header of request that would pe passed to webview, as:
if let url = URL(string: "https://yourdomain.com") {
var request = URLRequest(url: url)
request.addValue("auth token value", forHTTPHeaderField: "Authorization")
}

you can add to local storage: key is AccessToken and value is the token
webView.configuration.userContentController.addUserScript(script())
func script() -> WKUserScript {
let source = "localStorage.setItem('AccessToken', '\(getAccessToken())');"
return WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
}

I have created a viewcontroller which will create webview instance for you.
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
//Kind of crazy stuff
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
//General view with page, followed by link
let url = URL(string: "https://google.com")! //example
webView.load(URLRequest(url: url))
}
//I do not sure, is that ui?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
}
}

Related

Issue when setting cookies of WKWebView by copying cookies from HTTPCookieStorage to WKWebsiteDataStore

I want to show a website in a WKWebView in my iOS app for which I need to set specific http cookies for the user's session. The cookies are already stored in the shared HttpCookieStorage of the app.
At the moment, I try to copy them from there to the WKWebsiteDataStore of the web view before loading the website with the following code:
class ViewerViewController: UIViewController {
// MARK: - Properties
var webView: WKWebView!
// MARK: - Life Cycle
override func loadView() {
webView = WKWebView()
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
addCookiesToWebView {
print("Load website")
if let url = Bundle.main.url(forResource: "MyWebsite", withExtension: "html"), let html = try? String(contentsOf: url) {
self.webView.loadHTMLString(html, baseURL: url)
}
}
}
// MARK: - Functions
func addCookiesToWebView(completionHandler: #escaping (() -> Void)) {
let sharedCookies = HTTPCookieStorage.shared.cookies!
let webViewCookieStore = webView.configuration.websiteDataStore.httpCookieStore
var count = 0
sharedCookies.forEach { cookie in
DispatchQueue.main.async {
webViewCookieStore.setCookie(cookie) {
count += 1
print("Added cookie \(cookie.name)")
if count == sharedCookies.count {
completionHandler()
}
}
}
}
}
}
Now, what is incomprehensible for me, is that this sometimes works and the user is authenticated in his user session, but sometimes it doesn't.
It seems like there might be a race condition that leads to the web view loading the website before all the cookies have been copied? When I researched this problem I noticed that other people have problems with cookies in WKWebView as well, but I didn't find any solution so far that actually solves my issue.
Is there a bug in my code or is there a better approach for copying multiple cookies from HttpCookieStorage to a WKWebView?

Swift 4 WKWebView cookies fetch and delete synchronisation issue

I am making an app which makes decision of user login / logged out and other activities based on WkWebView cookies. Most of the time , it works fine. Sometimes , it failed to fetch cookies when login URL succeeded. And fails to delete the cookies when user logged out. Even , when i quickly login or logged out, it shows the wrong/previous token of the session.
My implementation is like :
func loadWebView () {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: UIScreen.main.bounds, configuration: webConfiguration )
webView.customUserAgent = APP_IDENTITY.appending("|") + Utility.deviceID().appending("|") + PSUserDefaults.getFCMToken()
webView.navigationDelegate = self
webView.uiDelegate = self
webView.load(DOMAIN_URL)
}
extension WKWebView {
func load(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
load(request)
}
}
func cleanAllCookies() {
HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
print("All cookies deleted")
WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
records.forEach { record in
WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {})
print("Cookie ::: \(record) deleted")
}
}
}
func refreshCookies() {
self.configuration.processPool = WKProcessPool()
}
func removeCookies(){
let cookie = HTTPCookie.self
let cookieJar = HTTPCookieStorage.shared
for cookie in cookieJar.cookies! {
cookieJar.deleteCookie(cookie)
print("removeCookies")
}
}
}
And the delegate is :
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// i am getting the cookies here most of the time. Sometimes , it failed to sync the cookies from here.
if #available(iOS 11.0, *) {
print(webView.configuration.websiteDataStore.httpCookieStore.getAllCookies({ (webViewCookies) in
let wkHttpCookieStorage = WKWebsiteDataStore.default().httpCookieStore;
wkHttpCookieStorage.getAllCookies { (cookies) in
// Nothing comes here sometimes !
for cookie in cookies {
}
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
print("decidePolicyFor navigationAction : \(navigationAction.request.url!)")
// Each URl navigation is happen properly on time
}
// I was checking the HTTPCookieStorage with a timer when it fails to get cookies in didFinish (wkwbeview ...) delegate method.
func checkHTTPCookieStorage (){
let cookieJar = HTTPCookieStorage.shared
for cookie in cookieJar.cookies! {
}
}
I also check the print(webView.configuration.websiteDataStore.httpCookieStore.getAllCookies({ (webViewCookies) in {} values with a timer when it fails to fetch cookies. Nothing works sometimes.
In logged out , i am deleting the cookies manually from extension method in all ways:
self.webView.cleanAllCookies()
self.webView.removeCookies()
Observation : Most of the case it gets cookies in login and can delete cookies in logged out. Sometimes it takes 3~10 second and get cookies when i apply a timer to fetch cookies. Sometimes it totally failed. I need to re launch the app then it gets cookies. This is embarrassing!
I have seen some blog, report , post on wkWebview cookies issues but nothing helped me.
My question :
How i can get/delete cookies all the time properly ?
Any wrong with my implementation ?
Thanks all.
I found some strange behaviour in different iOS version. Some iOS version saves cookies in
WKWebsiteDataStore.default().httpCookieStore
and some iOS version saves it in
HTTPCookieStorage.shared.cookies!
And it takes 3~10 seconds to receive/set cookies from web URL. I run a thread to check the cookies in both store. It works!

iOS - web view cookies not set

I am trying to set cookies in my iOS like this:
let url = URL(string: "url")!
let jar = HTTPCookieStorage.shared
let cookieHeaderField = ["Set-Cookie": "key1=value1, key2=value2"]
let cookies = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField, for: url)
jar.setCookies(cookies, for: url, mainDocumentURL: url)
let request = URLRequest(url: url)
viewerWebKit.load(request)
Then I am printing them like this:
viewerWebKit.configuration.websiteDataStore.httpCookieStore.getAllCookies( { (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
All cookies are printed and they seem to be set normally. But when I use the Web inspector of my safari to see, if they are really set then nothing is there. No cookies are set. What is the problem? Do I need to accept them? Is safari blocking them? How can I set them normally, to be visible in web inspector?
I also tried this approach:
import UIKit
import WebKit
class ViewWrapper: UIViewController, WKNavigationDelegate {
var loginToken: String?
#IBOutlet weak var viewerWebKit: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
var urlRequest = URLRequest(url: URL(string: "url")!)
urlRequest.httpShouldHandleCookies = true
let newcookie = HTTPCookie(properties: [
.domain: "domain",
.path: "",
.name: "key",
.value: "value",
.secure: "FALSE",
.expires: NSDate(timeIntervalSinceNow: 31556926)
])
viewerWebKit.configuration.websiteDataStore.httpCookieStore.setCookie(newcookie!, completionHandler: {
self.viewerWebKit.load(urlRequest)
})
viewerWebKit.configuration.websiteDataStore.httpCookieStore.getAllCookies( { (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func loadView() {
viewerWebKit = WKWebView()
viewerWebKit.navigationDelegate = self
view = viewerWebKit
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
cookieStore.getAllCookies({ (cookies) in
cookies.forEach({ (cookie) in
print(cookie.name)
})
})
}
}
but it didn't work too.
This is what I see in safari debug console:
Cookies are not set.
This is what I see in Xcode's console.
So here it seems to be set. But it is not in reality. Printing code prints cookies. But they are not all visible in safari console. How is that possible? Cookies csrftoken and sessionid are set by website, not by my app. And they are visible in both printing and debug console.
Set cookie through this
urlRequest.httpShouldHandleCookies = true
also after request create set
self.configuration.websiteDataStore.httpCookieStore.setCookie("your_http_cookie", completionHandler: {
// Do whatever you want. I suggest loading your webview after cookie is set.
})
Implement this observers WKHTTPCookieStoreObserver in your class.
WKWebsiteDataStore.default().httpCookieStore.add(self)
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
// Must implement otherwise wkwebview cookie not sync properly
self.httpCookieStore.getAllCookies { (cookies) in
cookies.forEach({ (cookie) in
// print your cookie here
})
}
}

Is it possible to check whether WKWebView has a specific text on it in Swift?

I am working on a project which is to show a website in a web view, the website is designed keeping iPhone in context and has only one URL where all the checks are done on server. I had a screen which has some text, for e.g. "Age". For this particular page, I want to add a tab bar button for sharing the application.
You should use evaluateJavaScript with the code in this answer, which returns all the text from the current page. After that, you can use contains to get whether the particular page contains the text you're looking for.
Don't forget to import WebKit and conform your class to WKNavigationDelegate.
func loadWebView() {
let webView = WKWebView()
webView.navigationDelegate = self
view.addSubview(webView)
let urlRequest = URLRequest(url: URL(string: "http://example.com")!)
webView.load(urlRequest)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.body.innerText") { result, error in
if let resultString = result as? String,
resultString.contains("Age") {
//site contains the text "Age"
}
}
}
}

Subclass UIWebView to Append Every Link (in Swift)

I was wondering how I could subclass UIWebView to append every link the user goes to with a string at the end like "=Human". To be clear, if the user goes to Yahoo.com and then clicks on any link it will still have the "=Human" at the end.
I could subclass UIWebView
But the docs say:
A WKWebView object displays interactive web content, such as for an
in-app browser.
For new development, employ this class instead of the older UIWebView
class.
This works for me:
import UIKit
import WebKit
class WebViewController: UIViewController,
WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
let webView = WKWebView()
webView.navigationDelegate = self
view = webView
let defaultUrl = NSURL(
string: "https://www.yahoo.com"
)
if let validDefaultUrl = defaultUrl {
let yahooRequest = NSURLRequest(URL: validDefaultUrl)
webView.loadRequest(yahooRequest)
}
}
//MARK: - WKNavigationDelegate methods:
func webView(webView: WKWebView,
decidePolicyForNavigationAction navigationAction: WKNavigationAction,
decisionHandler: (WKNavigationActionPolicy) -> Void)
{
print("[me]: In WKWebView delegate method")
if navigationAction.navigationType == .LinkActivated {
print("[me]: User clicked on a link.")
let request = navigationAction.request
if let url = request.URL,
newURL = NSURL(string: url.absoluteString + "=User")
{
print("[me]: New url is valid.")
print("[me]: New url is \(newURL.absoluteString)")
let newRequest = NSMutableURLRequest(URL: newURL)
decisionHandler(.Cancel)
webView.loadRequest(newRequest)
}
}
decisionHandler(.Allow)
}
}
Edit: Actually, the code does not enter the if navigationAction.navigationType == .LinkActivated block every time a link is clicked for some reason. Sometimes I see just the output:
[me]: In WKWebView delegate method
and I fail to see the output:
[me]: User clicked on a link.

Resources