GED125 GED125 - 4 months ago 36
Swift Question

Password is getting cached somewhere, need to clear it

I am working on an app that accesses a REST API Webservice. Everything is working great, except I recently started working on the the ability to logout and switch users and I've run into a strange situation. If I log out, and then click login again without entering the password it's working. I've even debugged the code and see that the password is blank, but the authentication is still working. Here is the code:

import UIKit
import LocalAuthentication

var userName = String()
var password = String()
var server = String()
var port = String()
var myUser = User()
var myExtensions = [ExtensionListItem]()
var myDevices = [Device]()

class LoginViewController: UIViewController, NSURLSessionDelegate, UITextFieldDelegate {

let authContext: LAContext = LAContext()

var logOutUser = Bool()

@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var serverNameField: UITextField!
@IBOutlet weak var usernameField: UITextField!
@IBOutlet weak var passwordField: UITextField!
@IBOutlet var loginEnable: UIButton!
var userPasswordString = NSString()
let userRequest = NSMutableURLRequest()
var userSession = NSURLSession()

override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(logoff), name: "logoff", object: nil)
if logOutUser {
NSUserDefaults.standardUserDefaults().setValue("", forKey: "password")
NSURLCache.sharedURLCache().removeAllCachedResponses()
userPasswordString = NSString()
}
//Determine if the user has a stored Username and populate the usernameField if possible
if NSUserDefaults.standardUserDefaults().objectForKey("userName") != nil{
usernameField.text = NSUserDefaults.standardUserDefaults().objectForKey("userName") as? String}
//Determine if the user has a stored ServerName and populate the serverNameField if possible.
if NSUserDefaults.standardUserDefaults().objectForKey("serverName") != nil{
serverNameField.text = NSUserDefaults.standardUserDefaults().objectForKey("serverName") as? String}
//Determin if the user has requested to use Touch ID
if (NSUserDefaults.standardUserDefaults().objectForKey("useTouchID") != nil) {
if NSUserDefaults.standardUserDefaults().valueForKey("useTouchID") as! Bool == true && CheckTouchIDCapable(){
//Trigger Touch ID
usernameField.enabled = false
passwordField.enabled = false
serverNameField.enabled = false
activityIndicator.startAnimating()
TouchIDCall()
}
}
// Do any additional setup after loading the view.
}

func logoff(){
NSURLCache.sharedURLCache().removeAllCachedResponses()
userSession.invalidateAndCancel()
}



override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
NSURLCache.sharedURLCache().removeAllCachedResponses()
// Dispose of any resources that can be recreated.
}

@IBAction func loginButton(sender: AnyObject) {

NSUserDefaults.standardUserDefaults().setObject(usernameField.text, forKey: "userName")
NSUserDefaults.standardUserDefaults().setObject(passwordField.text, forKey: "password")
NSUserDefaults.standardUserDefaults().setObject(serverNameField.text, forKey: "serverName")

if NSUserDefaults.standardUserDefaults().valueForKey("touchIDPreferenceSet") == nil && CheckTouchIDCapable() {
DisplayTouchIDQuestion("Use Touch ID?", message: "Would you like to use touch ID to login?")
}else{
usernameField.enabled = false
passwordField.enabled = false
serverNameField.enabled = false
activityIndicator.startAnimating()
CheckUser()
}
print("Password: \(password)")
print("Stored Password: \(NSUserDefaults.standardUserDefaults().valueForKey("password"))")
print("?? \(NSUserDefaults.standardUserDefaults().objectForKey("password"))")

}

func CheckUser(){


userName = (NSUserDefaults.standardUserDefaults().objectForKey("userName") as? String)!
if !logOutUser{
password = (NSUserDefaults.standardUserDefaults().objectForKey("password") as? String)!
}
server = (NSUserDefaults.standardUserDefaults().objectForKey("serverName") as? String)!
port = "8443"
// set up the base64-encoded credentials
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
userPasswordString = NSString(format: "%@:%@", userName, password)
let userPasswordData = userPasswordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCredential = userPasswordData!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let authString = "Basic \(base64EncodedCredential)"
config.HTTPAdditionalHeaders?.removeAll()
config.HTTPAdditionalHeaders = ["Authorization" : authString]
config.timeoutIntervalForRequest = 10.0


// create the user request
let userUrlString = NSString(format: "https://%@:%@/webserver/user/%@", server, port, userName)
let userUrl = NSURL(string: userUrlString as String)
userRequest.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
userRequest.URL = userUrl!
userRequest.HTTPMethod = "GET"
userRequest.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
userSession = NSURLSession(configuration: config, delegate: self, delegateQueue:NSOperationQueue.mainQueue())

//Send User Request to the server and populate labels with response.
_ = userSession.dataTaskWithRequest(userRequest) { (data, response, error) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in

if error?.code != nil{
print("ERROR: \(error!.localizedDescription)")
self.DisplayAlert("Error", message: error!.localizedDescription)
}else{
_ = NSString (data: data!, encoding: NSUTF8StringEncoding)
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
let accessDenied = Bool(dataString?.rangeOfString("HTTP Status 403").location != NSNotFound)
let authFailure = Bool(dataString?.rangeOfString("HTTP Status 401").location != NSNotFound)

if (authFailure || accessDenied) {
print("\(NSDate()): Unsuccessful Password Authentication Attempt for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.DisplayAlert("Access Denied", message: "Please Verify Your Credentials")
}else{
print("\(NSDate()): Successful Password Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
self.performSegueWithIdentifier("authenticated", sender: self)
}
}
})
}.resume()
}

func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
}

override func prefersStatusBarHidden() -> Bool {
return true
}

// MARK: - Keyboard Functions

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
}

func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
if textField == passwordField && usernameField.text != "" && serverNameField.text != ""{
loginButton(self)
}
return true
}

func ReEnableLogin(){

self.activityIndicator.hidesWhenStopped = true
self.activityIndicator.stopAnimating()
self.usernameField.enabled = true
self.passwordField.enabled = true
self.serverNameField.enabled = true
}

func DisplayAlert(title: String, message: String){

let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
self.ReEnableLogin()

}

func DisplayTouchIDQuestion(title: String, message: String){

let alertControllerQuestion = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alertControllerQuestion.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default, handler: { (action:UIAlertAction) in
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "useTouchID")
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "touchIDPreferenceSet")
NSUserDefaults.standardUserDefaults().setValue(self.passwordField.text, forKey: "touchIDCachedCredential")
self.CheckUser()
}))
alertControllerQuestion.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.Default, handler: { (action:UIAlertAction) in
NSUserDefaults.standardUserDefaults().setValue(false, forKey: "useTouchID")
NSUserDefaults.standardUserDefaults().setValue(true, forKey: "touchIDPreferenceSet")
self.CheckUser()
}))
self.presentViewController(alertControllerQuestion, animated: true, completion: nil)
}

func CheckTouchIDCapable()-> Bool {
var error: NSError?
var touchEnabledDevice: Bool = false
if authContext.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error){
touchEnabledDevice = true
}
return touchEnabledDevice
}

func TouchIDCall(){

authContext.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: "Place your finger on the Home button to log into Collaboration User Tools", reply: { (wasSuccessful, error) in
if wasSuccessful{
print("\(NSDate()): Successful Biometric Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
NSUserDefaults.standardUserDefaults().setValue(NSUserDefaults.standardUserDefaults().valueForKey("touchIDCachedCredential"), forKey: "password")
self.CheckUser()

}else{
print("\(NSDate()): Error: \(error!.code)")
print("\(NSDate()): Unsuccessful Biometric Authentication for user: \(NSUserDefaults.standardUserDefaults().valueForKey("userName")!)")
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.activityIndicator.stopAnimating()
})
})
self.ReEnableLogin()
}
})

}





}


I've tried:

NSURLCache.sharedURLCache().removeAllCachedResponses()
userSession.invalidatedAndCancel()


The logout table view controller calls this method:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
switch indexPath.row{
case 0:
myUser = User()
myExtensions.removeAll()
myDevices.removeAll()
NSUserDefaults.standardUserDefaults().setObject("", forKey: "password")
userName = ""
password = ""
NSURLCache.sharedURLCache().removeAllCachedResponses()
NSNotificationCenter.defaultCenter().postNotificationName("logoff", object: nil)
performSegueWithIdentifier("logout", sender: self)
default:
break
}
}


I don't know where the password is being cached. Any ideas?

Answer

My problem was never related to password caching. Most likely due to my relatively amateur experience level, I never considered the fact that it might be COOKIES keeping the session up. That's exactly what it ended up being. I solved the situation simply by deleting all cookies during the logout procedure. I added the following to my logout code and it's working perfectly.

print("\(NSDate()): Logout Requested, Deleting Cookies")
let cookieStorage = NSHTTPCookieStorage.sharedHTTPCookieStorage()
let cookies = cookieStorage.cookies! as [NSHTTPCookie]
print("\(NSDate()): Cookie count: \(cookies.count)")
for cookie in cookies{
   print("\(NSDate()): Deleting Cookie name: \(cookie.name) value: \(cookie.value)")
   NSHTTPCookieStorage.sharedHTTPCookieStorage().deleteCookie(cookie)
}