iOS - Ephemeral Session and UIWebView - ios

I'd like to load a web page into a UIWebView, using URLSession.
An ephemeral session, indeed, because i wouldn't like to store the session if, for example, i've a login on Twitter/FB/whatever site.
So, i've a UIWebView named webPage
#IBOutlet var webPage: UIWebView!
a UITextField, where i can write the URL
#IBOutlet var urlField: UITextField!
Next to this text field, i've a UIButton, that implements this action
#IBAction func sendUrl(_ sender: Any) {
let stringUrl = urlField.text
let url = URL (string: stringUrl!)
openPageWithSession(url: url!)
}
and the function openPageWithSession, where i use the ephemeral session
func openPageWithSession(url: URL){
let request = URLRequest(url: url)
let ephemeralConfiguration = URLSessionConfiguration.ephemeral
let ephemeralSession = URLSession(configuration: ephemeralConfiguration)
let task = ephemeralSession.dataTask(with: request) { (data, response, error) in
if error == nil {
self.webPage.loadRequest(request)
} else {
print("ERROR: \(error)")
}
}
task.resume()
}
The loading of the web page it's ok. But if i've a login on Twitter and after that i kill the app, if i reopen the app and navigate Twitter again, i'm already logged in!
Why? The ephemeral session is not invalidate after a kill?

From the comments above.
Instead of self.webPage.loadRequest(request), you should use:
self.webView.load(data, mimeType: "text/html", textEncodingName: "", baseURL: url).
This way you'll be using the ephemeral session you created. Otherwise you're just loading the web view normally, without the ephemeral session at all.

Related

How to create a secure PHP Web Service for access by iOS App

I am trying to figure out how to properly connect an iOS app to a MySQL database. I have the following code in my Model.
import Foundation
protocol HomeModelProtocol: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocol!
let urlPath = "http://hourofswift.com/service.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}
I have the PHP Web Service set up. That file is on the web server and has the credentials to access the database. What I am confused about is what I do if I don't want my database to be public. The iOS app is hitting the PHP Web Service and requires no credentials from the iOS app. That means anyone with an internet connection could send requests to the PHP Web Service. What do I do if I don't want the database to be public?

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: Adding SSL key to URLRequest

I'm trying to make a URLRequest but I need to add SSL key and password to the request but I haven't found any example of how to accomplish this.
This is my requests:
func requestFactory(request:URLRequest, completion:#escaping (_ data:Data?)->Void){
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, urlRequestResponse, error) in
if error != nil{
completion(data)
}
})
task.resume()
}
I'll really appreciate your help.
Swift 4
Assuming you have purchased yourself an SSL certificate, Google how to convert your SSL bundle certificate (.crt file) to .der format using OpenSSL in Terminal. Locate the .der file you created in your file system and drag it into your project folder in Xcode. Next, go to your project root and under Build Phases, click on the drop down list 'Copy Bundle Resources' and click the + button to add the .der file to the resource list.
Next you will need to make a class that implements URLSessionDelegate (in my case I called it URLSessionPinningDelegate) and when you formulate your URLSession call, you will pass in this class as the delegate.
You should have a look at how to implement SSL certificate pinning for instructions on how to implement this class. This site here has a perfect and functioning explanation of how to do that.
Below is an example of how to set up the session and task. The password will be passed in the Header of URLRequest when you call request.setValue so check out that documentation too. This should get you started once you've figured out SSL certificate pinning and have set up your backend to authenticate your user's password and also set up trust for your client-side certificate.
if let url = NSURL(string: "https://www.example.com") { // Your SSL server URL
var request = URLRequest(url: url as URL)
let password = "" // Your password value
request.setValue("Authorization", forHTTPHeaderField: password)
let session = URLSession(
configuration: URLSessionConfiguration.ephemeral,
delegate: URLSessionPinningDelegate(),
delegateQueue: nil)
With the added session and request parameters your code would look like this:
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil {
print("error: \(error!.localizedDescription): \(error!)")
} else if data != nil {
if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) {
print("Received data:\n\(str)")
} else {
print("Unable to convert data to text")
}
}
})
task.resume()

Assign network response sooner

I'm developing an app that relies on a web service backend. I have a separate class API to handle all network activities and send the data to the relevant view controllers.
I have a NSURLSession.dataTaskWithRequest() which sends my data via POST to my API and returns a response. I figured it would be best to have a single variable networkResponse in my API class with a getter and setter as I'll only ever need one response at a time.
In my ViewController, I have a login form which makes a request to the API and will log the user in depending on the response. If the credentials are incorrect, it returns the response in a UIAlertController.
On testing incorrect credentials, I've found that the response is not getting called in time for the AlertController, so it just shows a blank message. If I dismiss it and click login again straight away, It shows the response. How would I go about getting it to display sooner?
login function:
class func login (username: String, password: String) {
config.HTTPAdditionalHeaders = ["Authorization" : authorization]
let url : NSURL = NSURL(string: "\(root)PHP/login.php")!
let request = NSMutableURLRequest(URL: url)
var returnString : NSString = ""
request.HTTPMethod = "POST"
let postString = "username=\(username)&password=\(password)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
var networkTask = mySession.dataTaskWithRequest(request) {
data, response, error in
var err : NSError?
dispatch_async(dispatch_get_main_queue()) {
/* Setting response variable here */
self.setNetworkResponse(NSString(data: data, encoding: NSUTF8StringEncoding)!)
}
}
networkTask.resume()
}
calling API:
#IBAction func login(sender: AnyObject) {
if !txtUsername.text.isEmpty || !txtPassword.text.isEmpty {
API.login(txtUsername.text, password: txtPassword.text)
Alert:
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if identifier == "segueLogin" {
if API.getNetworkResponse() !== "true" {
var alert = UIAlertController(title: "Message", message: API.getNetworkResponse() as String, preferredStyle: .Alert)
While its probably not the most ideal solution, I've managed to fix the problem by setting a delay, using dispatch_after in the shouldPerformSegueWithIdentifier to delay the alert by about 5 seconds. The message appears first time now which is what I'm looking for.

Failure to return to calling function from NSURLSession delegate without killing the task

I'm using Swift in Xcode 6.2 (beta) but had the same problem on the 6.1 release version. I'm trying to use NSURLSession and believe I have it set up correctly (see code below). The problem is that I have a delegate setup to deal with a redirect happening through the code. I actually need to capture the cookies prior to the final redirection and I'm doing this through the delegate:
func URLSession(_:NSURLSession, task:NSURLSessionTask, willPerformHTTPRedirection:NSHTTPURLResponse, newRequest:NSURLRequest, completionHandler:(NSURLRequest!) -> Void )
This works and I'm able to execute code successfully and capture the cookies I need. The problem is that I need to add task.cancel() at the end of the function or else it never seems to complete and return to the delegator (parent?) function. Because of this I lose the results from the redirect URL (although in my current project it is inconsequential). The strange thing is that this was working for a while and seemingly stopped. I don't believe I entered any code that changed it, but something had to happen. Below is the relevant code.
NSURLSession Function:
func callURL (a: String, b: String) -> Void {
// Define the URL
var url = NSURL(string: "https://mycorrecturl.com");
// Define the request object (via string)
var request = NSMutableURLRequest(URL: url!)
// Use default configuration
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
// Create the NSURLSession object with default configuration, and self as a delegate (so calls delegate method)
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
// Change from default GET to POST (needed to call URL properly)
request.HTTPMethod = "POST"
// Construct my parameters to send in with the URL
var params = ["a":a, "b":b] as Dictionary<String, String>
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// Do some other stuff after delegate has returned...
})
task.resume()
return
}
The delegate code:
func URLSession(_:NSURLSession, task:NSURLSessionTask, willPerformHTTPRedirection:NSHTTPURLResponse, newRequest:NSURLRequest, completionHandler:(NSURLRequest!) -> Void ) {
// Check Cookies
let url = NSURL(string: "https://mycorrecturl.com")
var all = NSHTTPCookie.cookiesWithResponseHeaderFields(willPerformHTTPRedirection.allHeaderFields, forURL: url!)
// Get the correct cookie
for cookie:NSHTTPCookie in all as [NSHTTPCookie] {
if cookie.name as String == "important_cookie" {
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookie(cookie)
}
}
task.cancel()
}
It used to return to the calling function without calling task.cancel(). Is there anything that looks wrong with the code that would cause it to just hang in the delegate function if task.cancel() isn't called?
Edit: What code would I add to fix this.
If you are not canceling the request, your willPerformHTTPRedirection should call the completionHandler. As the documentation says, this completionHandler parameter is:
A block that your handler should call with either the value of the request parameter, a modified URL request object, or NULL to refuse the redirect and return the body of the redirect response.

Resources