Setting NSURLSessionConfiguration's HTTPShouldSetCookies to false disables cookie storage - ios

What I want to do is create an NSURLSession that does not automatically give a value to the Cookie HTTP header when I'm making an NSMutableURLRequest, because I set that value on my own. However, I want to be able to store the cookie that comes back to me when I make a sign in API request.
I use the following code to instantiate my session:
func createSession() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
// Set cookie policies.
configuration.HTTPCookieAcceptPolicy = .Always
configuration.HTTPCookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
configuration.HTTPShouldSetCookies = false
// ...
return NSURLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
}
When the NSURLSessionDataTask comes back to me in its completionHandler, I dig into the shared cookie storage to get the cookies that came with the response. However, I always get an empty array.
if let cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(response.URL!) {
println("cookies: \(cookies)") // Prints an empty array.
if let cookie = cookies.last as? NSHTTPCookie {
// I save the cookie to disk here.
}
}
Why is this happening even when I've set the NSURLSession to always accept cookies, and even provided a cookie storage?
I can only get the behavior that I want by setting the HTTPShouldSetCookies to true and supplying a Cookie HTTP header in my NSMutableURLRequest anyway. However, the Apple documentation on HTTPShouldSetCookies says that I should set it to false if I were to provide the cookie on my own in the request level:
If you want to provide cookies yourself, set this value to NO and provide a Cookie header either through the session’s HTTPAdditionalHeaders property or on a per-request level using a custom NSURLRequest object.

Related

How to set correct cookies Value against AVURLAssetHTTPCookiesKey while initialising AVURLAsset

I have to play HLS stream and it is secured/protected. In order to download manifest file and further ts file it is expected that request have valid Cookies information sent. I do receive Cookies information from server but it is not part of response header whereas it comes as part response String. It means Cookies automatically does not become part of NSHTTPStorage and application have to consume response string and fetch Cookies information from there; further need to be set while initialising AVURLAsset. As per AVURLAsst documentation in order to send Cookie information we should use options "AVURLAssetHTTPCookiesKey". I have mentioned in below code how to initialise AVURLAsset using this key
let cookieOptions = [AVURLAssetHTTPCookiesKey: Any]
let assets = AVURLAsset(url: url as URL, options: cookieArrayOptions)
My main problem how to set value against AVURLAssetHTTPCookiesKey. It take value of 'Any' type.Since Apple just mention it expect Any value and does not tell how internally it interpret and convert that value in correct format.
I tried two approach to set value: Say I have to set two Cookies key viz MyCookies1, MyCookies2
Approach 1 using NSHTTPStorage
let propertiesKey1 = [HTTPCookiePropertyKey.domain : "Domain=abc.com",HTTPCookiePropertyKey.path:"/",HTTPCookiePropertyKey.secure:true,HTTPCookiePropertyKey.init("HttpOnly"):true,HTTPCookiePropertyKey.value:"abc1",HTTPCookiePropertyKey.name:"MyCookies1"] as [HTTPCookiePropertyKey : Any]
let propertiesKey2 = [HTTPCookiePropertyKey.domain : "Domain=abc.com",HTTPCookiePropertyKey.path:"/",HTTPCookiePropertyKey.secure:true,HTTPCookiePropertyKey.init("HttpOnly"):true,HTTPCookiePropertyKey.value:"abc2",HTTPCookiePropertyKey.name:"MyCookies2"] as [HTTPCookiePropertyKey : Any]
let cookieKey1 = HTTPCookie(properties: propertiesKey1)
let cookieKey2 = HTTPCookie(properties: propertiesKey2)
HTTPCookieStorage.shared.setCookie(cookieKey1!)
HTTPCookieStorage.shared.setCookie(cookieKey2!)
let cookiesArray = HTTPCookieStorage.shared.cookies!
let cookieArrayOptions = [AVURLAssetHTTPCookiesKey: cookiesArray]
guard let url = URL(string:"abc.com") else { return }
let assets = AVURLAsset(url: url as URL, options: cookieArrayOptions)
But this is not working and AVPlayer just initializing and stopped working
Approach 2
let values = ["Cookie": "MyCookies1=abc1; MyCookies2=abc2"]
let cookieArrayOptions = [AVURLAssetHTTPCookiesKey: values]
guard let url = URL(string:"abc.com") else { return }
let assets = AVURLAsset(url: url as URL, options: cookieArrayOptions)
But this options always crashes.May be I am not setting values in correct format.
I have tried to provide as much information as possible but let me know if you need any further information.
After couple of tries I am able to make it working with Approach 1 usign NSHTTPStorage. I made a mistake of not using HTTPS streaming URL. I had already allowed to play non-HTTP url by passsing ATS and allow arbitrary loads. But AVURLAssetHTTPCookiesKey expect to provide only HTTPS url to be set for cookies. I used HTTPS streams and ultimately Cookies started to be sent internally in request header while downloading manifest/segment file in playing HLS stream

iOS Swift WKWebView Maintain User after login at app level

I am loading a url on webview, in which user will have to login. I am making a request by following way:
let urlStr = "https://flex-showcase.bloxcms.com/business/guebert-food-and-family-a-must-for-sundays-in-april/article_94462124-fbd4-5d0b-9841-880d82844c05.html"
var req = URLRequest(url: URL(string: urlStr)!)
req.addValue("1", forHTTPHeaderField: "X-Townnews-Now-API-Version")
let appdelegate = UIApplication.shared.delegate as! AppDelegate
let userAgent = appdelegate.appUserAgent
appdelegate.webviewObj.customUserAgent = userAgent
appdelegate.webviewObj.load(req)
after login we have seen that user session does not maintain properly. as we have seen that "X-Townnews-Now-API-Version" on all HTTP transactions is missing after login. Also I have observed that:
1) After reloading the webpage again every think works fine.
2) Also backend debugged that "login buttons are doing XHR, clicking on the sign-in button should have no bearing on HTTP requests. HTTP requests need to be intercepted at the webkit level and pass the right headers on all HTTP transactions."

how to support incognito/private mode in wkwebview/uiwebview

I am working on a incongnito browser.I am using wkwebview when I clear all the cookies I can see that popular search engine like google remembers the searches that has been made.
I tried cleaning all the cookies in NSHTTPCookieStorage and resetcookies using NSURLSession but its still not working.
Set nonpersistentdatastore for wkwebsitedatastore for wkwebviewconfiguration for wkwebview
Set NSURLrequestreloadcacheignoringlocalandremotecachedata for NSURlrequest in uiwebview
Reference
Creating a non-tracking in-app web browser
Private browsing in iOS using WKWebView
As per apple documentation:
To support private browsing,create a data store object and assign it to the websiteDataStore property of a WKWebViewConfiguration object before you create your web view. The default() method returns the default data store that saves website data persistently to disk. To implement private browsing, create a nonpersistent data store using the nonPersistent() method instead.
let webConfiguration = WKWebViewConfiguration()
webConfiguration.processPool = WKProcessPool()
webConfiguration.websiteDataStore = WKWebsiteDataStore.nonPersistent()
let webView = WKWebView(frame: self.webContainerView.bounds, configuration: webConfiguration)
// Set up request
if let requestURL = URL(string: "enter_url_to_load") {
var request = URLRequest(url: requestURL)
request.httpShouldHandleCookies = false
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
webView.navigationDelegate = self
webView.load(request)
}
self.webContainerView.addSubview(webView)

WKWebview injecting cookie header cause redirect loop

I'm trying to inject session cookies I've acquired separately into a WKWebview request, which turns out to be quite a pain...
I managed to inject the session cookies using this solution, as follows:
// Acquiring the cookies
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: s.request!.url!)
//Appending all the cookies into one raw string.
var cookiesRawString = ""
for c in cookies {
cookiesRawString += "\(c.name)=\(c.value); "
}
var req: URLRequest = try! URLRequest(url: URL, method: method)
// Then the injection itself
request.setValue(cookiesRawString, forHTTPHeaderField: "Cookie")
webView.load(req)
Let me quickly explain the server logic with pseudo code:
Server receive call to endpoint /endpoint1 with initial session cookies appended
It then proceed to redirect the client to /endpoint2 with user generated token appended in the url.
requesting the second endpoint with token appended results in the final redirect to /endpoint3 with Set-Cookie header containing one time session cookies
/endpoint3 with the one time session cookies appended results in 200 response, and the user is recognized.
The problem is that for some reason the when I append the cookies to the initial request using the method above, it results with a redirect loop, while on the Android platform it works flawless (I used there a similar injection method).
The only difference that I saw between them was that the android app injected the cookies only on the initial request, and all the subsequent redirect calls were without those session cookies.
While the ios repeated the initial session cookies on all the redirect calls (even ignoring the server set-cookie header and appending the initial session cookies..).
Am I doing something wrong? How can I make the wkwebview use the injected cookies only on the initial request?
Edit 1: Also tried falling back to UIWebview, but it produces the same results, it seems that injecting the cookies as a header is not good, but I tried using HTTPCookieStorage, but it won't save the cookies!
// the count is 7
var cookiesCount = HTTPCookieStorage.shared.cookies(for: s.request!.url!)?.count
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: s.request!.url!)
for c in cookies {
HTTPCookieStorage.shared.setCookie(c)
}
// Count is still 7!
cookiesCount = HTTPCookieStorage.shared.cookies(for: s.request!.url!)?.count
Edit 2:
Well I found out the UIWebview is using a global instance of the cookie storage, the same as alamofire (which I've used to fetch the session cookies), so there was no need to add the cookies manually, the site recognized the user.
But I still prefer to use WKWebview, as the UIWebview memory leak sky rockets (over 100 mb after couple of web pages navigation!).
Is there a way to use the global cookie jar (used by alamofire) in WKWebview??
I think this might be unrelated, but i had a similar issue.
The reason was the modified cookie was messing up all my subsequent request using NSURL.sharedSession. It turns out the cookie set using WKWebView was wiping out the headers from NSURLSession.sharedSession. I think the cookie storage is shared across multiple sessions. So, i ended up using EphemeralSession, instead of sharedSession.
I managed to get it working on WKWebview, using a hackish solution:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction:
WKNavigationAction, decisionHandler:
#escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url!.absoluteString
if UserAppendix.isLogin && url != previousNavigateUrl {
previousNavigateUrl = url
if url.contains("/endpoint1") {
let headerFields = navigationAction.request.allHTTPHeaderFields
let headerIsPresent = headerFields!.keys.contains("Cookie")
if headerIsPresent {
decisionHandler(WKNavigationActionPolicy.allow)
} else {
var req = URLRequest(url: navigationAction.request.url!)
let cookies = NetworkAppendix.httpSessionCookies
let values = HTTPCookie.requestHeaderFields(with: cookies)
req.allHTTPHeaderFields = values
webView.load(req)
decisionHandler(WKNavigationActionPolicy.cancel)
}
}
else if firstTime {
firstTime = false
let req = URLRequest(url: navigationAction.request.url!)
webView.load(req)
decisionHandler(WKNavigationActionPolicy.cancel)
}
else {
decisionHandler(.allow)
}
}
else {
decisionHandler(.allow)
}
}
I set the cookies on the first request, and on the second request break the flow and create a new request (with the redirect url), to avoid the session cookies I set on the first request, all subsequent requests are treated the same.
I know it's not perfect, but it gets the job done.

Alamofire clear all cookies

I need the user to be able to log out. When they log in now, a cookie gets saved automatically, that works fine. But I want to clear all cookies.
NSURLCache.sharedURLCache().removeAllCachedResponses()
This does not work
You can remove all the cookies specifically stored for an URL like this (Swift 3):
let cstorage = HTTPCookieStorage.shared
if let cookies = cstorage.cookies(for: url) {
for cookie in cookies {
cstorage.deleteCookie(cookie)
}
}

Resources