New to iOS app building using swiftui and rest api to build a new app. After going through many artilces etc wrote this for login screen
SignIn class
import Foundation
enum AuthenticationError: Error{
case invalidCredentials
case custom(errorMessage : String)
}
class SignIn {
struct LoginRequest : Codable
{
let UserName : String
let Password : String
}
struct LoginResponse : Codable
{
let AuthToken : String?
let Message : String?
let IsSuccess : Bool?
}
func SignInUser(UserName : String, Password : String, completion : @escaping (Result<String,
AuthenticationError>)->Void)
{
guard let url = URL(string: "http://xx.xxx.xxx.x:port/api/home/login")
else
{
completion(.failure(.custom(errorMessage: "Invalid URL")))
return
}
let body = LoginRequest (UserName: UserName, Password: Password)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let encodeData = try? JSONEncoder().encode(body)
print(String(data: encodeData!, encoding: .utf8)!)
request.httpBody = encodeData
URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let data = data , error == nil else
{
completion(.failure(.custom(errorMessage: "No Data")))
return
}
guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data)
else
{
completion(.failure(.invalidCredentials))
return
}
guard let token = loginResponse.AuthToken else
{
completion(.failure(.invalidCredentials))
return
}
completion(.success(token))
}.resume()
}
}
SignInViewModel
import Foundation
class SignInViewModel : ObservableObject {
@Published var username : String = ""
@Published var password : String = ""
func LogIn()
{
SignIn().SignInUser(UserName: username, Password: password)
{ **Line:#17**
result in
switch result{
case .success(let token):
print(token)
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
SignInActivity
import SwiftUI
struct SignInActivity: View {
@StateObject private var loginVM = SignInViewModel()
@State private var Username:String = ""
@State private var Password:String = ""
var body: some View {
VStack(){
Image("logo").resizable().aspectRatio(contentMode:.fit)
Spacer()
Text("Welcome! Sign In to Continue ")
.font(.system(size: 20)).fontWeight(.semibold)
.foregroundColor(Color.blue)
Spacer()
VStack(){
HStack(alignment: .center){
Image(systemName: "person.circle.fill")
.foregroundColor(Color.blue)
.padding(.leading,8)
TextField(
"username", text:$loginVM.username)
.font(.system(size: 14))
.padding(.top,10)
.padding(.bottom,10)
// .overlay(VStack{
// Divider().offset(x:0,y:15)
// })
}.background(Color("text_bg"))
.cornerRadius(10)
.padding(.trailing,25)
.padding(.leading,25)
.padding(.bottom,15)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
HStack(alignment: .center)
{
Image(systemName: "lock.circle.fill")
.foregroundColor(Color.green)
.padding(.leading,8)
TextField(
"password", text:$loginVM.password
).font(.system(size: 14))
.padding(.top,10)
.padding(.bottom,10)
// .overlay(VStack{
// Divider().offset(x:0,y:15)
// })
}.background(Color("text_bg"))
.cornerRadius(10)
.padding(.trailing,25)
.padding(.leading,25)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
}
HStack()
{
Button(action: {
loginVM.LogIn()
})
{
Text("Sign In")
.padding(7)
.font(Font.system(size: 15))
.foregroundColor(.white).background(Color.green).cornerRadius(6).shadow(radius: 5)
}
}.padding(.top)
Spacer()
Spacer()
}.ignoresSafeArea(.keyboard)
}
The API is working fine as I have already made the complete app in android(released) and it working fine. Now trying to made the app for iOS.
On debug the error is
completion () 0x0000000106b27820 ScreenLearning`closure #1 (Swift.Result<Swift.String,
ScreenLearning.AuthenticationError>) -> () in ScreenLearning.SignInViewModel.LogIn() -> () at
SignInViewModel.swift:17
on run the error is AutheticationError 1.
Cant figure out what is going on.
CodePudding user response:
To do basic authentication, try using the following approach, instead of insecurely sending the user and password as json in the request body. (note: userName and password in lowercase)
func SignInUser(userName : String, password : String, completion : @escaping (Result<String, AuthenticationError>)->Void) {
// -- here
guard !userName.isEmpty && !password.isEmpty else {
completion(.failure(.custom(errorMessage: "Invalid userName or password")))
return
}
// -- here should use https
guard let url = URL(string: "http://xx.xxx.xxx.x:port/api/home/login")
else {
completion(.failure(.custom(errorMessage: "Invalid URL")))
return
}
// -- here
let hash = "\(userName):\(password)".data(using: .utf8)!.base64EncodedString()
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// -- here
request.addValue("Basic \(hash)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let data = data , error == nil else{
completion(.failure(.custom(errorMessage: "No Data")))
return
}
guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data)
else {
completion(.failure(.invalidCredentials))
return
}
// -- here
guard let token = loginResponse.ResponseData?.AuthToken else {
completion(.failure(.invalidCredentials))
return
}
completion(.success(token))
}.resume()
}
Note: you are using http
instead of the required https
.
Change to https
or add the appropriate NSAppTransportSecurity
in your Info.plist
.
EDIT:
Note, according to the response data, you should have this model:
struct ResponseData: Codable {
let AuthToken: String?
// ....
}
struct LoginResponse: Codable {
let Message: String?
let IsSuccess: Bool?
let ResponseData: ResponseData?
}