Authentication error/ purple warning when trying to segue after validating data from API - swift

I have created a POST request which validates if the username and password are correct through a StatusCode: 0 that comes from the response of the POST request if the data are correct, At the signInViewController class I have created the button signInSegueToDashboard which when pressed must validate the data and if the data are valid then the user will be logged in without any problem.
The button sender at signInViewController:
#IBAction func signInSegueToDashboard(_ sender: Any) {
APICallerPOST.shared.signInToAccount(username: emailTextField.text!, password: passwordTextField.text!) { (result, error) in
if let result = result {
if result.StatusCode == 0 {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") else {
return
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {_ in
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
}else if result.StatusCode == 5 {
print("error: \(error!.localizedDescription)")
}
}
}
}
When i press the button after typing the correct data it just does nothing and just shows a purple warning that is saying to put it on Main thread, When i did put on main thread the segue part then it doesn't validate the data at all instead it just logs you in without any validation.
the POST request from APICallerPOST class:
func signInToAccount(username: String, password: String, completion: #escaping (SignInResponse?, Error?) -> Void) {
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["User": username, "Password": password]
//create the url with NSURL
let url = URL(string: "https://censoredurl/Signin")!
//create the session object
let session = URLSession.shared
//now create the Request object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to data object and set it as request body
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
//HTTP Headers
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(nil, error)
return
}
guard let data = data else {
completion(nil, NSError(domain: "dataNilError", code: -100001, userInfo: nil))
return
}
do {
//create json object from data
let decoder = JSONDecoder()
guard let json = try? decoder.decode(SignInResponse.self, from: data) else {
completion(nil, NSError(domain: "invalidJSONTypeError", code: -100009, userInfo: nil))
return
}
print(json)
completion(json, nil)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
})
task.resume()
}
Confused a lot.

The dataTask is asynchronous, and so is the code it runs in the completion handler. However all updates to UI need to be performed on the main thread, and so the parts of the completion handler that update the UI need to be pushed back onto the main thread.
In your case you could do it like this:
if result.StatusCode == 0 {
DispatchQueue.main.async {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {_ in
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
}
// ...
However it seems you are trying to use the Timer to delay presentation of the viewController, and there is a better way of doing this than using the Timer. You can use a delayed execution with DispatchQueue's asyncAfter(deadline:qos:flags:execute:) method:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// do your UI work
}

Related

log-in after checking the specific response from the API-POST request

I am having problems with POST request function I made, The problem is that I don't know how to use that specific response of StatusCode that comes after validating a user, I made the function that sets-up the POST request but now I need to make the statement to check if the StatusCode is for example 0 the user gets logged in if its 1 it shows error.
My POST request:
func signInToAccount(username: String, password: String, completion: #escaping ([String: Any]?, Error?) -> Void) {
let parameters = ["User": username, "Password": password]
let url = URL(string: "https://randomurl/Signin")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(nil, error)
return
}
guard let data = data else {
completion(nil, NSError(domain: "dataNilError", code: -100001, userInfo: nil))
return
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else {
completion(nil, NSError(domain: "invalidJSONTypeError", code: -100009, userInfo: nil))
return
}
print(json)
completion(json, nil)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
})
task.resume()
}
My sign-in button sender of sign-in viewController:
#IBAction func signInSegueToDashboard(_ sender: Any) {
APICallerPOST.shared.signInToAccount(username: "admin", password: "admin123") { (result, error) in
if let result = result {
print("success: \(result)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
self.activityLoaderSignIn.startAnimating()
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") else {
return
}
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) {_ in
self.activityLoaderSignIn.stopAnimating()
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
}
}
Now the problem is here:
I don't know how to implement a proper way of logging-in, I need for example the if statement to check for the StatusCode if its 0 then the rest of code executed and to the dashboard if its error then show the error.
APICallerPOST.shared.signInToAccount(username: "admin", password: "admin123") { (result, error) in
if let result = result {
print("success: \(result)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
The response that comes from the POST request:
{
"StatusCode": 0,
"Result": {
"First": "admin",
"Last": "admin123",
"CompleteName": "admin admin123",
"PhoneNumber": "+000 (00) 000-000",
"Email": "admin.admin123#gmail.com",
"IsConnectedToCustomer": true,
"Token": "a3311cc231994f34bfjksadf82f7a4djska3",
"TokenExpireDate": "2023-05-19T13:49:15.383"
}
}
Thanks in advance for those who contribute to help on this specific topic, And to all the newcomers this post will help a lot if the correct answer is found.
Create a decodable struct which holds your server's response
struct SignInResponse: Decodable {
struct SignInResult: Decodable {
let First: String
let Last: String
let CompleteName: String
let PhoneNumber: String
let Email: String
let IsConnectedToCustomer: Bool
let Token: String
let TokenExpireDate: String
}
let Result: SignInResult?
let StatusCode: Int
}
IMPORTANT: This requires that your server either responds with an Result object as you have posted OR (when login failed) with "Result": null or with no Result object at all. But not an empty string or something like that.
Then modify the completion closure definition of your signInToAccount function to
func signInToAccount(username: String, password: String, completion: #escaping (SignInResponse?, Error?) -> Void) { ...
Inside this function, use a JSONDecoder to decode the response from your server.
let decoder = JSONDecoder()
guard let json = try? decoder.decode(SignInResponse.self, from: data) else {
completion(nil, NSError(domain: "invalidJSONTypeError", code: -100009, userInfo: nil))
return
}
print(json)
completion(json, nil)
Now you can access the StatusCode property in your function call's closure.
signInToAccount (username: "admin", password: "admin123") { (result, error) in
if let result = result {
print ("StatusCode is", result.StatusCode)
if let signInData = result.Result {
print ("Complete name:", signInData.CompleteName)
}
print("success: \(result)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}

Authentication with POST request is failing to validate data

So I have a created a POST request which validates the Username and Password when someone tries to login, The problem is when i press the button sender it doesn't validate the data at all even if the Username and Password field are empty it still segues you to the main Dashboard which I find it very weird.
The Button Sender from signInViewController class :
#IBAction func signInSegueToDashboard(_ sender: Any) {
APICallerPOST.shared.signInToAccount(username: emailFieldSignIn.text!, password: passwordFieldSignIn.text!) { [self] (result, error) in
switch result?.StatusCode {
case 0:
DispatchQueue.main.async {
activityLoaderSignIn.startAnimating()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
self.activityLoaderSignIn.stopAnimating()
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
case 1:
print("error")
case 2:
print("error2")
case 3:
print("error3")
case 4:
print("error4")
case 5:
print("error5")
default:
break
}
}
}
The problem is that even if the emailFieldSignIn.text! and passwordFieldSignIn.text! are empty it still segues you to the mainTabBarController without any validation of the data.
The POST request from APICallerPOST class :
func signInToAccount(username: String, password: String, completion: #escaping (SignInResponse?, Error?) -> Void) {
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["User": username, "Password": password]
//create the url with NSURL
let url = URL(string: "https://censoredurl/Signin")!
//create the session object
let session = URLSession.shared
//now create the Request object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to data object and set it as request body
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
//HTTP Headers
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(nil, error)
return
}
guard let data = data else {
completion(nil, NSError(domain: "dataNilError", code: -100001, userInfo: nil))
return
}
do {
//create json object from data
let decoder = JSONDecoder()
guard let json = try? decoder.decode(SignInResponse.self, from: data) else {
completion(nil, NSError(domain: "invalidJSONTypeError", code: -100009, userInfo: nil))
return
}
print(json)
completion(json, nil)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
})
task.resume()
}
Please check my answer it will be helpfull for you I have added compulsory checks to validate if UITextfields are empty or not you can also add valid email check.
#IBAction func signInSegueToDashboard(_ sender: Any) {
if emailFieldSignIn.text!.isEmpty || passwordFieldSignIn.text!.isEmpty{
// show some error
return
}
APICallerPOST.shared.signInToAccount(username: emailFieldSignIn.text!, password: passwordFieldSignIn.text!) { [self] (result, error) in
switch result?.StatusCode {
case 0:
DispatchQueue.main.async {
activityLoaderSignIn.startAnimating()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
self.activityLoaderSignIn.stopAnimating()
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
case 1:
print("error")
case 2:
print("error2")
case 3:
print("error3")
case 4:
print("error4")
case 5:
print("error5")
default:
break
}
}
}

How to create proper completion handler for server login in swift?

I have an api manager class in my swift application and it has a server login with username and password.
I want to know how to create a completion handler for it that when the server responses with 200 status code, the function handles that response and for example performs a segue in the viewcontroller.
I did not find any tutorials for this. Thanks for your help!
EDIT 1:
What i need is: The completion handler is immediately run when the function is called. I want the completion handler run after server responds.
And this is my login function:
public class func Login(username: String, password: String, complitionHandler: #escaping (Int) -> Void) {
let urlS = "http://server.com/" + "login.php"
let url = URL(string: urlS)
var request = URLRequest(url: url!)
request.httpMethod = "POST"
let body = "username=\(username.lowercased())&password=\(password)"
request.httpBody = body.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print(error!)
print("error")
logedIn = 2
return
}
do{
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let parseJson = json {
let code = parseJson["status"] as! String
if code == "200" {
print("loged inn")
logedIn = 1
}else if code == "400" {
print("uuuser/pass error")
logedIn = 0
}
}
}catch{
print("json error")
logedIn = 2
}
}
task.resume()
DispatchQueue.main.async {
complitionHandler(logedIn)
}
}
And how i call the function in my ViewController:
Manager.Login(username: "1", password: "1") { (i) in
switch i {
case 0:
print("user/pass error")
case 1:
print("loged in")
self.performSegue(withIdentifier: "toMain", sender: self)
case 2:
print("json error")
default:
()
}
}
You have all of the pieces in place. You just need to move your call to the completion handler to the correct place:
}catch{
print("json error")
logedIn = 2
}
DispatchQueue.main.async {
complitionHandler(logedIn)
}
}
task.resume()
Also note that method names should start with lowercase letters so your Login function should be named login.
Now you can use this login method like:
login(username: someUsername, password: somePassword) { (result) in
if result == 1 {
// success - do your segue
} else if result == 0 {
// bad username/password
} else {
// some error
}
}

Get user authentication before next UI View will appear

I am working on user authentication process but i stuck in the moment when reciving data from rest with token. Whenever i create the new task it does not enter on the first time into the function but after creating it skipping doing smth else which is showing a next hooked up UIViewController to segue.
My rest service with post method hashing user password, creating json, URL request and at the end creating URLSession. How could i wait for finish of this task ? To not let to do anything else before it is not complited ?
EDIT
I've added OpeartionQueue to liquidate nil's from next view.
func postLogin(name:String, pass:String, completion: #escaping (Bool) -> () ) {
let md5Data = self.MD5(string:pass)
let hashPass = md5Data!.map { String(format: "%02hhx", $0) }.joined()
let json: [String: Any] = ["username": name,
"passwordHash": hashPass ]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
// create post request
let url = URL(string: LOGIN_URL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// insert json data to the request
request.httpBody = jsonData
request.setValue("application/json;charest=utf-8", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
let message:String = responseJSON["message"] as! String
if !(message.range(of: "ERROR") != nil){
SessionMenager.Instance.token = message
completion(true)
}
} else{
print(error.debugDescription)
}
}
task.resume()
}
Then simply in my LoginViewController action with button :
#IBAction func LoginButton(_ sender: Any) {
let username = usernameTextField.text
let password = passwordTextField.text
if username == "" {
AlertWindow(title: "Username", message: "Wrong username")
} else if password == "" {
AlertWindow(title: "Password", message: "Wrong password")
} else {
let usernameToUpper = username!.uppercased()
RestService.Instance.postLogin(name: usernameToUpper, pass: password!, completion: { sth in
if sth {
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "mapSegue", sender: self)
}
} else {
return
}
})
}
}
The segue was hooked up into LoginButton which took me instantly to the next page. I've changed it into hooking up all view controllerr.
Thanks!
Because your segue is hooked up into LoginButton, it will automatically show the next viewController once you press the button.
Just hoop up the segue to the whole viewController and it should work.

Swift communicating with multiple webservices

I currently need to use a web service to do some tasks which are, logging in and receiving a list of information.
After logging in successfully the web service will return 'response' information : {"LoginID":"1","Password":"","Role":"pol","LoginType":"Indevidual","UserID":"6110895204062016","UserRoleID":"20202020202020","RoleID":"999674512042008","PartyId":"1063081525122008","PartyFunctionId":"123123","BranchCode":"10","RoleCode":"123123","Status":{"isError":false,"ErCode":null,"Error":null}}
which is needed to be sent to another web service to get a list of information.
Currently using the login button to call the webserivce to be able to login.
How do I call another webservice using the information from the first webservice?
Code for a better idea:
#IBAction func GetPolicyListButton(_ sender: Any) {
//I will need the information from the second web service to display after clicking this button.. how?
}
#IBAction func LoginButton(_ sender: Any) {
let postString = "cpr=\(usernameField.text!)&password=\(passwordField.text!)"
let url = URL(string:"http://login")!
let postData:Data = postString.data(using: String.Encoding.utf8, allowLossyConversion: false)!
let postLength:String = String(postData.count) as String
var request:URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = postData
request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(error)")
return
}
let httpStatus = response as? HTTPURLResponse
print("statusCode should be 200, but is \(httpStatus!.statusCode)")
print("response = \(response!)")
print(postString)
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString!)")
let start = responseString!.index(responseString!.startIndex, offsetBy: 75)
let end = responseString!.index(responseString!.endIndex, offsetBy: -9)
let range = start..<end
let jsonStr = responseString!.substring(with: range)
print(jsonStr)
let data1 = jsonStr.data(using: .utf8)!
_ = try? JSONSerialization.jsonObject(with: data1) as? [String: Any]
let persondata = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
let personInfodata = persondata as? [String : Any]
_ = personInfodata?[""] as? [String : Any]
if (responseString?.contains("1001"))!{
DispatchQueue.main.async {
print("incorrect - try again")
let alert = UIAlertController(title: "Try Again", message: "Username or Password Incorrect", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
else{
DispatchQueue.main.async {
print("correct good")
let storyboard = UIStoryboard(name: "Maintest", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "correctone")
self.present(controller, animated: true, completion: nil)
}
}
}
task.resume()
}
You are experiencing the complexity of not working in MVC. While writing an application if you do not properly use MVC the complexity and unnecessary duplication of code can get out of hand and you lose oversight.
A style for example to use is, is to create a LoginModel and a ItemsModel for lack of a better name. Both will be making web requests so be sure to create a class that handles a generic web request or implement a framework like Alamofire (which has some great examples for authentication and automatic retrying of requests based on Tokens etc)
Now in your ViewController seperate all the handling of your data to a View-Independant LoginClass like this:
#IBAction func LoginButton(_ sender: UIButton) {
guard let username = usernameField.text else { print("no username") ; return }
guard let password = passwordField.text else { print("no password") ; return }
self.loginModel.login(username: username, password: password) { [weak self] success in
if success {
let dataModel = dataModel(credentials: credentialStyle)
dataModel.loadItems { items : [Item]? in
// Dispatch items to main queue
}
}
}
}
Now in your loginModel you handle the login and in a completely separate model you handle the dataModel which you instantiate with the credentials you received from the loginModel. Off course this is a rough example and using Alamofire you can use a Session Manager for example which will take care of the authentication (see the URL of 'automatic retrying of requests', scroll down a little bit and there is an example of authentication.) removing the need of instantiating your dataModel with credentials put this is purely to demonstrate how to split up your code to handle these requests.