As the title is saying, im working on a login. The tokenHandler is already working and im using a KeychainAccess.
Here my tokenHandler class:
import KeychainAccess
class TokenHandler {
func saveUsernameToKeyChain(username: String) {
do {
try keychain.set(username, key: "myUsername")
} catch let error {
print(error)
}
}
func getUsernameFromKeyChain() -> String? {
return keychain[string: "myUsername" ]
}
func saveUserPasswordToKeyChain(password: String) {
do {
try keychain.set(password, key: "UserPassword")
} catch let error {
print(error)
}
}
func getUserPasswordFromKeyChain() -> String? {
return keychain[string: "UserPassword"]
}
let keychain = Keychain(service: "com.mybackendpage")
func getTokenFromKeyChain() -> String? {
return keychain[string: "myToken"]
}
func saveTokenToKeyChain(token: String) {
do {
try keychain.set(token, key: "myToken")
}
catch let error {
print(error)
}
}
func saveRefreshTokenToKeyChain(refreshToken: String) {
do {
try keychain.set(refreshToken, key: "myRefreshToken")
}
catch let error {
print(error)
}
}
func loginToAPI(username: String, password: String) -> Any {
guard let url = URL(string: "https:mypage.com") else
{
return ""
}
let jsonData = try? JSONSerialization.data(withJSONObject: [
"email": username,
"password": password
])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
// insert json data to the request
request.httpBody = jsonData
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { print(error!.localizedDescription); return }
guard let data = data else { print("Empty data"); return }
if let str = String(data: data, encoding: .utf8) {
print(str)
}
}.resume()
return "TOKENSTRING"
}
}
And here my LoginVC class:
class LoginViewController: UIViewController {
let tokenHandler = TokenHandler()
#IBOutlet weak var usernameTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
let username = tokenHandler.getUsernameFromKeyChain()
let userPassword = tokenHandler.getUserPasswordFromKeyChain()
#IBAction func unwindToLogin(_ unwindSegue: UIStoryboardSegue) {
print("-unwinding success-")
}
// func for the login button:
#IBAction func loginButton(_ sender: UIButton) {
activityIndicator.startAnimating()
loginWithCredentials()
let token = tokenHandler.getTokenFromKeyChain()
if token != nil {
performSegue(withIdentifier: "segueToNavigation", sender: self)
} else if ( token == nil ){
// create the alert:
let alert = UIAlertController(title: "Wrong login data", message: "Please try again.", preferredStyle: UIAlertController.Style.alert)
// add an action to the button:
alert.addAction(UIAlertAction( title: "Ok", style: UIAlertAction.Style.default, handler: nil ))
// show the alert:
self.presentingViewController
print("-Token could not be created.-")
}
else {
// create the alert:
let alert = UIAlertController(title: "Wrong login data", message: "Please try again.", preferredStyle: UIAlertController.Style.alert)
// add an action to the button:
alert.addAction(UIAlertAction( title: "Ok", style: UIAlertAction.Style.default, handler: nil ))
// show the alert:
self.presentingViewController
print("-Token could not be created.-")
}
}
func loginWithCredentials() {
let username: String = usernameTextField.text!
let password: String = passwordTextField.text!
let authResponse = tokenHandler.loginToAPI(username: username, password: password)
}
}
Im still not skilled swift programmer, so I will be happy if any of you could give me some good advices. I was reading and trying to work with the delegate principle, but frankly, my guts are telling me, that this is not what I need.
I was reading about
PerformSegueWithIdentifier
but not really understand how to transform it into my code...
The segues which I have included storyboardwise are working, but unfortunately also, if the test user didn't do the login. So, im pressing the login button w/o any username and userpwd and im getting anyway to the next View. Not cool, so help me please :)
EDIT: I changed performSegue to shouldPerformSegue but im still getting access to the next View w/o any permission.
EDIT: Im getting:
-Token could not be created.-
{"message":"The given data was invalid.","errors":{"email":["The email field is required."],"password":["The password field is required."]}}
So the error is correct, but by pressing on the "Login" Button im still getting to the next View.
EDIT:
Ive tried a few changes, now I have for eg:
if tokenHandler.getTokenFromKeyChain() != nil
instead of
let token = tokenHandler.getTokenFromKeyChain()
if token != nil
Apparently, nothing what im doing in this IBAction for the LoginButton does anything different. What am I missing?
Well, it looks to me, that if you did once call saveTokenToKeyChain() ever since you've been running the app on your device/simulator, then the KeyChain will hold some string there, as I can't see where you set it to nil (I can't see where you set it at all, but let's suppose that you deleted the code that saves the token). So what your current logic does is that it performs the segue if you have some string saved as token (no matter if it's empty string, random string or an expired token). Whatever was left there, getTokenFromKeyChain() will return you a string so your if token != nil will always evaluate to true.
On the other hand, if you clear the KeyChain data, as I can't find any piece of code that saves the token, the UI will always say that the login fails, even if it actually succeeds.
So you should handle the login success/failure with properly writing/deleting the token to/from KeyChain.
Related
I need to get a value from an extension before i click on a button that goes to another screen, how can i do that?
This is the IBAction inside viewController. When i click it makes a request to an API then send a value to global variable on the second screen:
#IBAction func enter(_ sender: UIButton) {
if let login = loginTextfield.text, let password = passwordTextfield.text {
loginManager.performLoginRequest(login, password)
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
}
}
extension LoginViewController: LoginManagerDelegate {
func didUpdateLogin(with login: LoginModel) -> (Bool, String) {
success = login.success
token = login.token
return (success, token)
}
}
Manager:
import Foundation
protocol LoginManagerDelegate {
func didUpdateLogin(with login: LoginModel) -> (Bool, String)
}
struct LoginManager {
var delegate: LoginManagerDelegate?
func performLoginRequest(_ login: String, _ password: String) {
let url = URL(string: "https://private-anon-1a0df64d9c-ibmfc.apiary-mock.com/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = """
{\n "username": "\(login)",\n "password": "\(password)"\n}
""".data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response {
print(response)
if let data = data, let body = String(data: data, encoding: .utf8) {
print(body)
if let login = self.parseJSON(loginData: data) {
self.delegate?.didUpdateLogin(with: login)
}
}
} else {
print(error ?? "Unknown error")
}
}
task.resume()
}
func parseJSON(loginData: Data) -> LoginModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(LoginData.self, from: loginData)
let success = decodedData.success
let token = decodedData.data.token
let login = LoginModel(success: success, token: token)
return login
} catch {
print(error.localizedDescription)
return nil
}
}
}
My problem is, this extension is just being called after i click the button. This way resultsViewController.receivedToken is not getting the value from token.
So, how can i call didUpdateLogin (to pass it values to success and token) before clicking on the IBAction?
THe reason for this behaviour is the background thread you are using:
(1) You call loginManager.performLoginRequest(login, password) which then starts a background thread to actually work on that request.
(2) In the meantime your code continues to run, executing resultsViewController.receivedToken = token.
(3) Since (1) is not done yet, your token is still nil (or an old token).
One of many possible solutions:
Add a block to the parameters of performLoginRequest in which you call
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
This way you make sure that code is only called after(!) the login was successful because you wait for it. In the meantime you could show a loading spinner or something similar. Login is a task where a user simply has to wait, there is usually (depending on the app) no way around it.
The code could look something like this in the end:
loginManager.performLoginRequest(login, password) {
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
}
whereas your LoginManager would have a method like
func performLoginRequest(_ login: String,
_ password: String,
completion: #escaping () -> Void)
which is then used later in your Dispatch:
DispatchQueue.main.async {
let loginVC = LoginViewController()
loginVC.didUpdateLogin(login: login)
completion()
}
I am trying to switch the LogInViewController to the homescreen by using a segue with an identifier after selecting a key from the json response I am getting back from my database. I get no errors however there is a warning that says
"Cast from 'String?' to unrelated type '[String : String]' always fails"
which I believe is the problem.
The log after clicking the login button says this:
"Result: SUCCESS
{"status":true,"message":"Successful Login"}
{"user":{"userID":3,"email":"becky1","Final-Score":1,"Game-Reminder":1,"Stat-Update":0,"Game-Start":0}}"
My full code:
import UIKit
import Alamofire
import GoogleSignIn
import SwiftyJSON
class LogInViewController: UIViewController,GIDSignInUIDelegate, GIDSignInDelegate {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var btnGoogleSignIn:UIButton!
//you can get the ip using ifconfig command in terminal
let URL_USER_LOGIN = "http://cgi.sice.indiana.edu/~team58/login.php"
let defaultValues = UserDefaults.standard
#IBOutlet weak var textFieldEmail: UITextField!
#IBOutlet weak var textFieldPassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
///Google Sign in////
btnGoogleSignIn.addTarget(self, action: #selector(signinUserUsingGoogle(_:)), for: .touchUpInside)
// Do any additional setup after loading the view, typically from a nib.
}
#objc func signinUserUsingGoogle(_ sender: UIButton) {
if btnGoogleSignIn.title(for: .normal) == "Sign Out" {
GIDSignIn.sharedInstance().signOut()
lblTitle.text = ""
btnGoogleSignIn.setTitle("Sign in Google", for: .normal)
} else {
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().signIn()
}
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
print("We have error signing in user == \(error.localizedDescription)")
} else {
performSegue(withIdentifier: "popOutNoteExpanded", sender: self)
if let gmailUser = user {
lblTitle.text = "You are signed in using id \(gmailUser.profile.email)"
btnGoogleSignIn.setTitle("Sign Out", for: .normal)
/// end google sign in ///
}
}
}
#IBAction func LoginButton(_ sender: UIButton) {
let parameters: Parameters=[
"email":textFieldEmail.text!,
"password":textFieldPassword.text!
]
//making a post request
Alamofire.request(URL_USER_LOGIN, method: .post, parameters: parameters).responseString
{
response in
// print("Request: \(String(describing: response.request))") // original url request
// print("Response: \(String(describing: response.response))") // http url response
print("Result: \(response.result)")
print(response.result.value!)
//
// //self.ResponseLabel.text = "Result: \(response.result)"
if let result = response.result.value as? [String:String] {
//if there is no error
// if result["SUCCESS"] {
if result["message"] == "Succesful Login" {
//getting the user from response
//getting user values - not neccesary
if let userEmail = result["email"] {
self.defaultValues.set(userEmail, forKey: "email")
}
if let userPassword = result["password"] {
self.defaultValues.set(userPassword, forKey: "password")
}
self.performSegue(withIdentifier: "popOutNoteExpanded", sender: self)
//
} else {
//error message in case of invalid credential
// original url request
// print("Response: \(String(describing: response.response))") // http url response
print("invalid credentials")
}
}
}
}
}
The problem is that response.result.value has got optional String type. But you tries to cast is as Dictionary of [String: String] here
if let result = response.result.value as? [String:String] {
This code checks it safely but the cast obviously fails.
To resolve this issue you have to parse the incode string as json object in order to retrieve the data.
Swift 4 uses Codable to do this work. Documentation or use Alamofire API which does the same work: this method responseJSON
There are plenty of ways to cast an optional string into a dictionary.
You can use the native JSONSerialization
guard let text = response.result.value as? String else { return }
if let data = text.data(using: String.Encoding.utf8) {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:String]
print(json) // safely use json
} catch {
print("Error")
}
}
I am currently implementing a signup feature into a chat application I am working on. In my 'SignupViewController' I want to implement a function named 'signupButtonPressed' which routes me from a signup viewcontroller to a 'ListContacts' viewcontroller. If the signup fails, then a function called 'showValidationError' will execute. Code excerpt from my SignupViewController below:
#IBAction func signupButtonPressed(_ sender: Any) {
let request = Signup.Request(
name: fullNameTextField.text!,
email: emailTextField.text!,
password: passwordTextField.text!
)
interactor?.createAccount(request: request)
}
func showValidationError(_ message: String) {
let alertCtrl = UIAlertController(title: "Oops! An error occurred", message: message, preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
self.show(alertCtrl, sender: self)
}
I am using Swift Clean Architecture, so I will link the code to my Signup Router, Model, and Interactor files also:
1) Signupinteractor.swift:
import Foundation
protocol SignupBusinessLogic {
func createAccount(request: Signup.Request)
}
class SignupInteractor: SignupBusinessLogic {
var viewController: SignupFormErrorLogic?
var router: (NSObjectProtocol & SignupRoutingLogic)?
var worker = UsersWorker()
func createAccount(request: Signup.Request) -> Void {
self.worker.signup(request: request) { user, error in
guard error == nil else {
print(error!)
self.viewController?.showValidationError("Error creating account!")
return
}
self.router?.routeToListContacts()
}
}
}
2) SignupModels.swift:
import Foundation
enum Signup {
struct Request {
var name: String
var email: String
var password: String
}
struct Response {
var user: User?
init(data: [String:Any]) {
self.user = User(
id: data["id"] as! Int,
name: data["name"] as! String,
email: data["email"] as! String,
chatkit_id: data["chatkit_id"] as! String
)
}
}
}
3) SignupRouter.swift:
import UIKit
#objc protocol SignupRoutingLogic {
func routeToListContacts()
}
class SignupRouter: NSObject, SignupRoutingLogic {
weak var viewController: SignupViewController?
func routeToListContacts() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyboard.instantiateViewController(withIdentifier: "MainNavigator") as! UINavigationController
viewController!.show(destinationVC, sender: nil)
}
}
The signup function in my UsersWorker.swift file:
func signup(request: Signup.Request, completionHandler: #escaping (User?, UsersStoreError?) -> Void) {
let params: Parameters = [
"name": request.name,
"email": request.email,
"password": request.password
]
postRequest("/api/users/signup", params: params, headers: nil) { data in
guard data != nil else {
return completionHandler(nil, UsersStoreError.CannotSignup)
}
let response = Signup.Response(data: data!)
CurrentUserIDDataStore().setID(CurrentUserID(id: response.user?.chatkit_id))
let request = Login.Account.Request(
email: request.email,
password: request.password
)
self.login(request: request) { token, error in
guard error == nil else {
return completionHandler(nil, UsersStoreError.CannotLogin)
}
DispatchQueue.main.async {
completionHandler(response.user, nil)
}
}
}
}
When I enter signup details into the signup UITextfields (fullNameTextField; emailTextField; passwordTextField) and press the signup button, an error called 'CannotSignup' triggers. Unsure why however. This case can also be found in my UsersWorker.swift file:
enum UsersStoreError: Error {
case CannotLogin
case CannotSignup
case CannotFetchChatkitToken
}
Would be great if anyone is able to look over the code to get an idea for what the issue might be, and how I might resolve it? If any further info is required just ask!
Most probably the API call is failing, Please make a check on return HTTP status code instead of data. In some cases the API call can be success without any response data
Ideally send an Error instance as well along with data back from postRequest method
I'm trying to upload image url from Firebase Storage to Realtime database.
Here's the code
#IBOutlet weak var jobTitle: UITextField!
#IBOutlet weak var companyName: UITextField!
#IBOutlet weak var jobLocation: UITextField!
#IBOutlet weak var ImageView1stPoster: UIImageView!
var imageUploaded = Data()
var URLtoRealtime = ""
func addPost() {
ref.child("Poster").childByAutoId().setValue(["jobName": jobTitle.text as Any,
"companyTitle": companyName.text as Any,
"jobLocation": jobLocation.text as Any,
"firstPoster": URLtoRealtime as Any,
/*,
"timeStamp":[".sv":"timestamp"]*/]
as[String:Any])
}
// DoneButton to submit everthing :)
#IBAction func DoneButton(_ sender: Any) {
uploadImageToFirebase(imageData: imageUploaded)
createAlert(title: "Post has been submitted", message: "Going to home page")
addPost()
}
func uploadImageToFirebase(imageData: Data) {
// References and vars
let StorageRefrenece = Storage.storage().reference()
let currentUser = Auth.auth().currentUser
let posterImageRef = StorageRefrenece.child("posters").child(currentUser!.uid).child("posterOne.jpg")
let uploadMetaData = StorageMetadata()
uploadMetaData.contentType = "image/jpeg"
// putData to put data to the server using MetaData to orignize everthing.
posterImageRef.putData(imageData, metadata: uploadMetaData) { (uploadedImageMeta, error) in
if error != nil {
print("Error Took place \(String(describing: error?.localizedDescription))")
return
} else {
print("metaData of uploaded image \(uploadMetaData)")
}
}
posterImageRef.downloadURL { (url, error) in
if (error != nil) {
// Handle any errors
print(error!.localizedDescription)
print("NOOOPPPEEE")
} else {
// Get the download URL for 'images/stars.jpg'
print("Working Good")
let UrlString = url!.absoluteString
print(UrlString)
self.URLtoRealtime = UrlString
}
}
}
I'm trying like to make URLString = URLtoRealtime data and then
add the post to firebase.
but what is happening is that it executes addPost() function before
self.URLtoRealtime = UrlString
I don't know how to make the program to execute the previous line of code before addpost() function.
When the done button is touched, you're asynchronously uploading the image to firebase and downloading the URL. However, as you've pointed out, by the time you addPost, the URL hasn't been downloaded yet – you want to do one after the other.
#IBAction func DoneButton(_ sender: Any) {
uploadImageToFirebase(imageData: imageUploaded) { [weak self] (url, error) in
if let url = url {
createAlert(title: "Post has been submitted", message: "Going to home page")
self?.URLtoRealtime = url.absoluteString
self?.addPost()
} else {
self?.createAlert(title: "Post could not be submitted", message: "Try again")
}
}
}
We can add a completion argument to the upload method so that you can addPost once the upload and download of the URL is finished.
func uploadImageToFirebase(imageData: Data, completion: #escaping (URL?, Error?) -> ()) {
guard let uid = Auth.auth().currentUser?.uid else { return completion(nil, nil) }
let posterImageRef = Storage.storage().reference(withChild: "posters/\(uid)/posterOne.jpg")
let uploadMetaData = StorageMetadata(dictionary: [ "contentType": "image/jpeg" ])
posterImageRef.putData(imageData, metadata: uploadMetaData) { (metadata, error) in
if let ref = metadata?.storageReference {
ref.downloadURL(completion: { (url, error) in
completion(url, error)
})
} else {
completion(nil, error)
}
}
}
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?
I think your issue lies on part of your logic when handling your password. First, in your func CheckUser() you assign the value of var password if !logOutUser from your user defaults, however this property (password) is not getting cleared at any point, so if !logOutUser is false (which it looks like always is, as its not getting set anywhere), and your input fields are empty, then it will use the previous value that you had store in there.
So if I am correct, and the culprit here is that the password field is storing its data, a quick, dirty fix would be to simply clear these fields (username and password) after you perform your request. A better fix would be to directly pass the usernameField.text value directly and clear it after its done. (I'm unsure why you are storing your user's password in your app, which I would recommend reconsidering, if you need it for validating a session you should be using authentication tokens)
// set up the base64-encoded credentials
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
userPasswordString = NSString(format: "%#:%#", userName, password)
userName = ""
password = ""
I would also like to point out that you should be using optionals for these fields, you should be using optional unwrapping for your user defaults and also adding a validation mechanism so that you do not submit requests with empty fields, ideally your button would be disabled or you would alert your user of an invalid field.
Good Luck!
EDIT
Try this approach: rather than using a class property for the values of password username and server. Use method variables to set your values, this way these values will get disposed when the method terminates. (This is a bit of a shot in the dark as i dont have a compiler at hand so there might be some minor syntax errors). - note the method signature
func CheckUser(username: String, password: String, server: String){
let port = "8443"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let 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.
let task = 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()
}
Then you can call your method as so:
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(usernameField.text, password: passwordField.text, server: serverNameField.text)
}
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)
}